Điều gì thực sự xảy ra khi bạn không rảnh sau malloc?


538

Điều này đã là một cái gì đó đã làm phiền tôi từ lâu.

Tất cả chúng ta đều được dạy ở trường (ít nhất là tôi) rằng bạn PHẢI giải phóng mọi con trỏ được phân bổ. Tuy nhiên, tôi hơi tò mò về chi phí thực sự của việc không giải phóng bộ nhớ. Trong một số trường hợp rõ ràng, như khi mallocđược gọi bên trong vòng lặp hoặc một phần của thực thi luồng, điều đó rất quan trọng để giải phóng để không bị rò rỉ bộ nhớ. Nhưng hãy xem xét hai ví dụ sau:

Đầu tiên, nếu tôi có mã giống như thế này:

int main()
{
    char *a = malloc(1024);
    /* Do some arbitrary stuff with 'a' (no alloc functions) */
    return 0;
}

Kết quả thực sự ở đây là gì? Suy nghĩ của tôi là quá trình chết đi và sau đó không gian heap biến mất nên không có hại khi bỏ lỡ cuộc gọi free(tuy nhiên, tôi nhận ra tầm quan trọng của việc dù sao đi nữa để đóng, duy trì và thực hành tốt). Tôi có đúng trong suy nghĩ này không?

Thứ hai, giả sử tôi có một chương trình hoạt động giống như một cái vỏ. Người dùng có thể khai báo các biến như aaa = 123và các biến được lưu trữ trong một số cấu trúc dữ liệu động để sử dụng sau. Rõ ràng, có vẻ như rõ ràng rằng bạn sẽ sử dụng một số giải pháp sẽ gọi một số hàm * alloc (hashmap, danh sách được liên kết, đại loại như thế). Đối với loại chương trình này, sẽ không có ý nghĩa bao giờ miễn phí sau khi gọi mallocvì các biến này phải có mặt mọi lúc trong quá trình thực hiện chương trình và không có cách nào tốt (mà tôi có thể thấy) để thực hiện điều này với không gian được phân bổ tĩnh. Có phải là thiết kế tồi khi có một loạt bộ nhớ được phân bổ nhưng chỉ được giải phóng như một phần của quá trình kết thúc? Nếu vậy, những gì thay thế?


7
@NTDLS Sự kỳ diệu của hệ thống xếp hạng thực sự hoạt động được một lần: 6 năm và câu trả lời "xứng đáng hơn" đã thực sự vươn lên dẫn đầu.
zxq9

15
Những người bên dưới cứ nói rằng một hệ điều hành hiện đại tốt sẽ dọn dẹp nhưng nếu mã đang chạy ở chế độ kernel (ví dụ: vì lý do hiệu năng) thì sao? Các chương trình chế độ kernel (trong Linux chẳng hạn) có được sandbox không? Nếu không, tôi tin rằng bạn sẽ cần phải tự do giải phóng mọi thứ, tôi cho rằng, ngay cả trước khi có bất kỳ sự chấm dứt bất thường nào như với phá thai ().
Tiến sĩ Person Person II

3
@ Dr.PersonPersonII Có, mã chạy trong chế độ kernel thường phải tự do giải phóng mọi thứ.
zwol

1
Tôi muốn nói thêm rằng free(a)không thực sự làm bất cứ điều gì để thực sự giải phóng bộ nhớ! Nó chỉ đơn thuần đặt lại một số gợi ý trong việc triển khai libc của malloc, theo dõi các khối bộ nhớ có sẵn trong một trang bộ nhớ lớn (thường được gọi là "heap"). Trang đó vẫn sẽ chỉ được giải phóng khi chương trình của bạn chấm dứt chứ không phải trước đó.
Marco Bonelli

1
Free () có thể, hoặc có thể không, thực sự giải phóng bộ nhớ. Nó chỉ có thể đánh dấu khối là giải phóng, để được thu hồi sau hoặc có thể liên kết nó thành một danh sách miễn phí. Nó có thể hợp nhất nó thành các khối miễn phí liền kề hoặc có thể để lại việc phân bổ tiếp theo. Đó là tất cả một chi tiết thực hiện.
Jordan Brown

Câu trả lời:


378

Mọi hệ điều hành hiện đại sẽ phục hồi tất cả không gian bộ nhớ được phân bổ sau khi thoát khỏi chương trình. Ngoại lệ duy nhất tôi có thể nghĩ đến có thể là một cái gì đó giống như Palm OS, nơi bộ nhớ tĩnh và bộ nhớ thời gian chạy của chương trình khá giống nhau, vì vậy việc không giải phóng có thể khiến chương trình chiếm nhiều dung lượng hơn. (Tôi chỉ đang suy đoán ở đây.)

Vì vậy, nhìn chung, không có hại trong đó, ngoại trừ chi phí thời gian chạy để có nhiều bộ nhớ hơn bạn cần. Chắc chắn trong ví dụ bạn đưa ra, bạn muốn giữ bộ nhớ cho một biến có thể được sử dụng cho đến khi nó bị xóa.

Tuy nhiên, nó được coi là phong cách tốt để giải phóng bộ nhớ ngay khi bạn không cần nó nữa, và để giải phóng mọi thứ bạn vẫn có xung quanh khi thoát khỏi chương trình. Đây là một bài tập để biết bạn đang sử dụng bộ nhớ nào và suy nghĩ xem bạn có còn cần nó không. Nếu bạn không theo dõi, bạn có thể bị rò rỉ bộ nhớ.

Mặt khác, lời khuyên tương tự để đóng các tệp của bạn khi thoát có kết quả cụ thể hơn nhiều - nếu bạn không, dữ liệu bạn đã viết cho họ có thể không bị xóa hoặc nếu chúng là tệp tạm thời, họ có thể không bị xóa khi bạn hoàn thành. Ngoài ra, xử lý cơ sở dữ liệu nên có các giao dịch được cam kết và sau đó đóng lại khi bạn hoàn thành chúng. Tương tự, nếu bạn đang sử dụng một ngôn ngữ hướng đối tượng như C ++ hoặc Objective C, không giải phóng một đối tượng khi bạn hoàn thành nó sẽ có nghĩa là hàm hủy sẽ không bao giờ được gọi và bất kỳ tài nguyên nào mà lớp chịu trách nhiệm có thể không được dọn sạch.


16
Có lẽ cũng tốt khi đề cập rằng không phải ai cũng sử dụng hệ điều hành hiện đại, nếu ai đó lấy chương trình của bạn (và nó vẫn chạy trên HĐH không phục hồi bộ nhớ) thì chạy GG.
dùng105033

79
Tôi thực sự xem xét câu trả lời này sai. Một người nên luôn luôn phân bổ tài nguyên sau khi hoàn thành chúng, có thể là tập tin xử lý / bộ nhớ / mutexs. Khi có thói quen đó, người ta sẽ không phạm phải sai lầm đó khi xây dựng máy chủ. Một số máy chủ dự kiến ​​sẽ chạy 24x7. Trong những trường hợp đó, bất kỳ rò rỉ nào cũng có nghĩa là máy chủ của bạn cuối cùng sẽ hết tài nguyên đó và bị treo / sập theo một cách nào đó. Một chương trình tiện ích ngắn, ya một rò rỉ không phải là xấu. Bất kỳ máy chủ, bất kỳ rò rỉ là cái chết. Làm cho mình một việc. Dọn dẹp sau chính mình. Đó là một thói quen tốt.
EvilTeach

120
Phần nào của "Tuy nhiên, nó được coi là phong cách tốt để giải phóng bộ nhớ ngay khi bạn không cần nó nữa và để giải phóng mọi thứ bạn vẫn có xung quanh khi thoát khỏi chương trình." Bạn có nghĩ sai không?
Paul Tomblin

24
Nếu bạn có một bộ nhớ lưu trữ mà bạn cần ngay cho đến khi chương trình thoát ra và bạn không chạy trên một hệ điều hành nguyên thủy, thì giải phóng bộ nhớ ngay trước khi bạn thoát là một lựa chọn phong cách, không phải là khiếm khuyết.
Paul Tomblin

30
@Paul - Chỉ cần đồng ý với EvilTeach, không được coi là phong cách tốt để giải phóng bộ nhớ, không chính xác là không giải phóng bộ nhớ. Từ ngữ của bạn làm cho điều này có vẻ quan trọng như đeo khăn tay phù hợp với cà vạt của bạn. Trên thực tế, đó là về mức độ mặc quần.
Heath Hunnicutt

110

Có bạn đúng, ví dụ của bạn không gây hại gì (ít nhất là không phải trên hầu hết các hệ điều hành hiện đại). Tất cả bộ nhớ được phân bổ bởi quy trình của bạn sẽ được hệ điều hành phục hồi khi quá trình thoát.

Nguồn: Phân bổ và huyền thoại GC (cảnh báo PostScript!)

Huyền thoại phân bổ 4: Các chương trình không được thu gom rác phải luôn luôn phân bổ tất cả bộ nhớ mà chúng phân bổ.

Sự thật: Các thỏa thuận bị bỏ qua trong mã được thực thi thường xuyên gây ra rò rỉ ngày càng tăng. Chúng hiếm khi được chấp nhận. nhưng các chương trình giữ lại hầu hết bộ nhớ được phân bổ cho đến khi thoát khỏi chương trình thường hoạt động tốt hơn mà không có sự can thiệp nào. Malloc dễ thực hiện hơn nhiều nếu không có miễn phí.

Trong hầu hết các trường hợp, giải phóng bộ nhớ ngay trước khi thoát chương trình là vô nghĩa. Hệ điều hành sẽ lấy lại nó bằng mọi giá. Miễn phí sẽ chạm và trang trong các đối tượng chết; HĐH sẽ không.

Hậu quả: Hãy cẩn thận với "máy dò rò rỉ" đếm số lần phân bổ. Một số "rò rỉ" là tốt!

Điều đó nói rằng, bạn thực sự nên cố gắng tránh tất cả các rò rỉ bộ nhớ!

Câu hỏi thứ hai: thiết kế của bạn là ok. Nếu bạn cần lưu trữ một cái gì đó cho đến khi ứng dụng của bạn thoát thì bạn có thể thực hiện việc này với phân bổ bộ nhớ động. Nếu bạn không biết trả trước kích thước yêu cầu, bạn không thể sử dụng bộ nhớ được phân bổ tĩnh.


3
Có thể bởi vì câu hỏi, khi tôi đọc nó, là những gì thực sự xảy ra với bộ nhớ bị rò rỉ, chứ không phải là ví dụ cụ thể này có ổn không. Mặc dù vậy, tôi sẽ không bỏ phiếu vì nó vẫn là một câu trả lời hay.
DevinB 17/03/2016

3
Có lẽ là đã có (Windows sớm, Mac OS sớm) và có thể vẫn là các HĐH yêu cầu các quá trình giải phóng bộ nhớ trước khi thoát nếu không không gian không được lấy lại.
Pete Kirkham 17/03/2016

Hoàn toàn ổn, trừ khi bạn quan tâm đến việc phân mảnh bộ nhớ hoặc hết bộ nhớ - bạn làm điều này quá nhiều và hiệu suất ứng dụng của bạn sẽ biến mất. Bên cạnh những sự thật khó khăn, luôn luôn tuân theo thực tiễn tốt nhất và xây dựng thói quen tốt.
NTDLS 17/03/2016

1
Tôi có một câu trả lời được chấp nhận hiện đang ngồi khoảng -11, vì vậy anh ấy thậm chí không chạy trong hồ sơ.
Paul Tomblin 17/03/2016

8
Tôi nghĩ thật sai lầm khi giải thích sự cần thiết của bộ nhớ miễn phí bằng cách nói "vì trình phát hiện rò rỉ". Điều này giống như nói rằng "bạn phải lái xe chậm trong một con phố vui chơi bởi vì những người cảnh sát có thể đang đợi bạn bằng một chiếc camera tốc độ".
Sebastian Mach

57

=== Điều gì về việc chứng minhtái sử dụng mã trong tương lai ? ===

Nếu bạn không viết mã để giải phóng các đối tượng, thì bạn đang giới hạn mã chỉ an toàn khi sử dụng khi bạn có thể phụ thuộc vào bộ nhớ được giải phóng bởi quá trình bị đóng ... tức là sử dụng một lần nhỏ các dự án hoặc các dự án "vứt bỏ" [1] ) ... nơi bạn biết khi nào quá trình sẽ kết thúc.

Nếu bạn làm viết mã mà miễn phí () là tất cả bộ nhớ cấp phát động của bạn, sau đó bạn là tương lai chống mã và để cho người khác sử dụng nó trong một dự án lớn hơn.


[1] liên quan đến các dự án "vứt bỏ". Mã được sử dụng trong các dự án "Ném đi" có cách không bị vứt đi. Điều tiếp theo bạn biết mười năm đã trôi qua và mã "vứt đi" của bạn vẫn đang được sử dụng).

Tôi đã nghe một câu chuyện về một số người viết một số mã chỉ để giải trí để làm cho phần cứng của anh ta hoạt động tốt hơn. Anh ấy nói " chỉ là một sở thích, sẽ không lớn và chuyên nghiệp ". Nhiều năm sau, rất nhiều người đang sử dụng mã "sở thích" của anh ấy.


8
Downvote cho "các dự án nhỏ". Có nhiều dự án lớn rất cố tình không giải phóng bộ nhớ khi thoát vì thật lãng phí thời gian nếu bạn biết các nền tảng mục tiêu của mình. IMO, một ví dụ chính xác hơn sẽ là "các dự án bị cô lập". Ví dụ: nếu bạn đang tạo một thư viện có thể tái sử dụng sẽ được bao gồm trong các ứng dụng khác, không có điểm thoát nào được xác định rõ ràng vì vậy bạn không nên rò rỉ bộ nhớ. Đối với một ứng dụng độc lập, bạn sẽ luôn biết chính xác khi nào quá trình kết thúc và có thể đưa ra quyết định có ý thức trong việc giảm tải dọn dẹp cho HĐH (phải thực hiện kiểm tra theo bất kỳ cách nào).
Dan Bechard

Ứng dụng của ngày hôm qua là chức năng thư viện ngày nay và ngày mai nó sẽ được liên kết thành một máy chủ tồn tại lâu dài, được gọi là hàng ngàn lần.
Adrian McCarthy

1
@AdrianMcCarthy: Nếu một hàm kiểm tra xem một con trỏ tĩnh có phải là null hay không, hãy khởi tạo nó malloc()nếu có và chấm dứt nếu con trỏ vẫn là null, một hàm như vậy có thể được sử dụng một cách an toàn một số lần tùy ý ngay cả khi freekhông bao giờ được gọi. Tôi nghĩ rằng có lẽ đáng để phân biệt rò rỉ bộ nhớ có thể sử dụng hết dung lượng lưu trữ không giới hạn, so với các tình huống chỉ có thể lãng phí một lượng lưu trữ hữu hạn và có thể dự đoán được.
supercat

@supercat: Nhận xét của tôi đã nói về việc thay đổi mã theo thời gian. Chắc chắn, rò rỉ một lượng bộ nhớ giới hạn không phải là một vấn đề. Nhưng một ngày nào đó, ai đó sẽ muốn thay đổi chức năng đó để nó không còn sử dụng con trỏ tĩnh. Nếu mã không có điều khoản để có thể giải phóng bộ nhớ trỏ, đó sẽ là một thay đổi khó khăn (hoặc tệ hơn, thay đổi sẽ rất tệ và bạn sẽ bị rò rỉ không giới hạn).
Adrian McCarthy

1
@AdrianMcCarthy: Thay đổi mã không còn sử dụng con trỏ tĩnh có thể sẽ yêu cầu di chuyển con trỏ vào một loại đối tượng "bối cảnh" nào đó và thêm mã để tạo và phá hủy các đối tượng đó. Nếu con trỏ luôn luôn nullnếu không có phân bổ tồn tại và không có giá trị khi phân bổ tồn tại, việc có mã miễn phí phân bổ và đặt con trỏ đến nullkhi bối cảnh bị phá hủy sẽ rất đơn giản, đặc biệt là so với mọi thứ khác cần phải được thực hiện để di chuyển các đối tượng tĩnh vào một cấu trúc bối cảnh.
supercat

52

Bạn đã đúng, không có hại gì và chỉ cần thoát ra nhanh hơn

Có nhiều lý do khác nhau cho việc này:

  • Tất cả các môi trường máy tính để bàn và máy chủ chỉ cần giải phóng toàn bộ không gian bộ nhớ khi thoát (). Họ không biết về các cấu trúc dữ liệu nội bộ chương trình như đống.

  • Hầu như tất cả các free()triển khai không bao giờ trả lại bộ nhớ cho hệ điều hành.

  • Quan trọng hơn, thật lãng phí thời gian khi được thực hiện ngay trước khi thoát (). Khi thoát, các trang bộ nhớ và không gian trao đổi được phát hành đơn giản. Ngược lại, một loạt các cuộc gọi () miễn phí sẽ đốt cháy thời gian của CPU và có thể dẫn đến các hoạt động phân trang đĩa, lỗi bộ nhớ cache và trục xuất bộ đệm.

Về possiblility mã tương lai tái sử dụng justifing sự chắc chắn của ops vô nghĩa: đó là một xem xét nhưng nó cho là không phải là Agile cách. YAGNI!


2
Tôi đã từng làm việc trong một dự án mà chúng tôi đã dành một khoảng thời gian ngắn để cố gắng hiểu cách sử dụng bộ nhớ chương trình (chúng tôi được yêu cầu hỗ trợ, chúng tôi đã không viết nó). Dựa trên kinh nghiệm tôi đã đồng ý với viên đạn thứ hai của bạn. Tuy nhiên, tôi muốn nghe bạn (hoặc ai đó) cung cấp thêm bằng chứng rằng điều này là đúng.
dùng106740

3
Đừng bận tâm, tìm thấy câu trả lời: stackoverflow.com/questions/1421491/ . Cảm ơn bạn!
dùng106740

@aviggiano được gọi là YAGNI.
v.oddou

Nguyên tắc YAGNI hoạt động theo cả hai cách: Bạn sẽ không bao giờ cần tối ưu hóa đường dẫn tắt máy. Tối ưu hóa sớm và tất cả những thứ đó.
Adrian McCarthy

26

Tôi hoàn toàn không đồng ý với tất cả những người nói OP là chính xác hoặc không có hại.

Mọi người đang nói về một hệ điều hành hiện đại và / hoặc cũ.

Nhưng nếu tôi ở trong một môi trường mà đơn giản là tôi không có HĐH thì sao? Ở đâu không có gì?

Hãy tưởng tượng bây giờ bạn đang sử dụng các ngắt theo kiểu luồng và phân bổ bộ nhớ. Trong tiêu chuẩn C ISO / IEC: 9899 là tuổi thọ của bộ nhớ được nêu là:

7.20.3 Chức năng quản lý bộ nhớ

1 Thứ tự và sự liên tục của lưu trữ được phân bổ bởi các lệnh gọi liên tiếp đến các hàm calloc, malloc và realloc là không xác định. Con trỏ được trả về nếu việc phân bổ thành công được căn chỉnh phù hợp để nó có thể được gán cho một con trỏ tới bất kỳ loại đối tượng nào và sau đó được sử dụng để truy cập vào một đối tượng hoặc một mảng các đối tượng đó trong không gian được phân bổ (cho đến khi không gian được giải quyết rõ ràng) . Thời gian tồn tại của một đối tượng được phân bổ kéo dài từ khi phân bổ cho đến khi thỏa thuận. [...]

Vì vậy, không cần phải cho rằng môi trường đang làm công việc giải phóng cho bạn. Nếu không, nó sẽ được thêm vào câu cuối cùng: "Hoặc cho đến khi chương trình kết thúc."

Vì vậy, nói cách khác: Không giải phóng bộ nhớ không chỉ là thực hành xấu. Nó tạo ra mã không di động và không tuân thủ C. Mà ít nhất có thể được xem là "chính xác, nếu như sau: [...], được hỗ trợ bởi môi trường".

Nhưng trong trường hợp bạn hoàn toàn không có HĐH, không ai làm việc cho bạn (tôi biết nói chung bạn không phân bổ và phân bổ lại bộ nhớ trên các hệ thống nhúng, nhưng có những trường hợp bạn có thể muốn.)

Vì vậy, nói chung về đồng bằng C (như OP được gắn thẻ), điều này chỉ đơn giản là tạo ra mã sai và không di động.


4
Một lập luận ngược lại là nếu bạn là một môi trường nhúng, bạn - với tư cách là nhà phát triển - sẽ khó tính hơn rất nhiều trong việc quản lý bộ nhớ của bạn ngay từ đầu. Thông thường, điều này là đến điểm thực sự phân bổ trước bộ nhớ cố định tĩnh thay vì có bất kỳ mallocs / reallocs nào trong thời gian chạy.
John Go-Soco

1
@lunarplasma: Mặc dù những gì bạn đang nói không phải là không chính xác, nhưng điều đó không làm thay đổi thực tế những gì tiêu chuẩn ngôn ngữ đang nêu, và tất cả những người hành động chống lại / hơn nữa, có thể theo lẽ thường, là sản xuất mã hạn chế. Tôi có thể hiểu nếu ai đó nói "Tôi không phải quan tâm đến nó", vì có đủ trường hợp nó ổn. NHƯNG người ta ít nhất nên biết TẠI SAO anh ta không phải quan tâm. và đặc biệt không bỏ qua nó miễn là một câu hỏi không liên quan đến trường hợp đặc biệt đó. Và vì OP đang hỏi về C nói chung dưới khía cạnh lý thuyết (trường học). Sẽ không ổn khi nói "Bạn không cần"!
dhein

2
Trong hầu hết các môi trường không có HĐH, không có phương tiện nào có thể "chấm dứt" chương trình.
supercat

@supercat: Như tôi đã viết trước đây: Bạn nói đúng về nó. Nhưng nếu ai đó hỏi về lý do giảng dạy và các khía cạnh của trường học, thì không đúng khi nói "Bạn không cần phải suy nghĩ về điều đó trong hầu hết thời gian không quan trọng" Từ ngữ và hành vi của ngôn ngữ định nghĩa được đưa ra vì một lý do và chỉ vì hầu hết các môi trường xử lý nó cho bạn, bạn không thể nói rằng không cần phải quan tâm. Đó là quan điểm của tôi.
dhein

2
-1 để trích dẫn tiêu chuẩn C trong khi hầu hết không áp dụng khi không có hệ điều hành, vì không có thời gian chạy để cung cấp các tính năng cho các nhiệm vụ tiêu chuẩn, đặc biệt là về quản lý bộ nhớ và các chức năng thư viện tiêu chuẩn (rõ ràng cũng không có cùng với thời gian chạy / HĐH).

23

Tôi thường miễn phí mọi khối được phân bổ một khi tôi chắc chắn rằng tôi đã hoàn thành nó. Hôm nay, điểm vào chương trình của tôi có thể là main(int argc, char *argv[]), nhưng ngày mai nó có thể foo_entry_point(char **args, struct foo *f)và được gõ như một con trỏ hàm.

Vì vậy, nếu điều đó xảy ra, bây giờ tôi có một rò rỉ.

Về câu hỏi thứ hai của bạn, nếu chương trình của tôi lấy đầu vào như a = 5, tôi sẽ phân bổ không gian cho a hoặc phân bổ lại cùng một không gian trên a = "foo" tiếp theo. Điều này sẽ vẫn được phân bổ cho đến khi:

  1. Người dùng đã gõ 'unset a'
  2. Chức năng dọn dẹp của tôi đã được nhập, phục vụ tín hiệu hoặc người dùng gõ 'thoát'

Tôi không thể nghĩ ra bất kỳ HĐH hiện đại nào không lấy lại được bộ nhớ sau khi quá trình thoát ra. Sau đó, một lần nữa, free () là giá rẻ, tại sao không dọn dẹp? Như những người khác đã nói, các công cụ như valgrind là tuyệt vời để phát hiện rò rỉ mà bạn thực sự cần phải lo lắng. Mặc dù các khối bạn ví dụ sẽ được gắn nhãn là 'vẫn có thể truy cập', nhưng nó chỉ gây ra tiếng ồn khi bạn cố gắng đảm bảo bạn không bị rò rỉ.

Một huyền thoại khác là " Nếu nó là chính (), tôi không phải giải phóng nó ", điều này không chính xác. Hãy xem xét những điều sau đây:

char *t;

for (i=0; i < 255; i++) {
    t = strdup(foo->name);
    let_strtok_eat_away_at(t);
}

Nếu điều đó xuất hiện trước khi rèn / daemonizing (và trên lý thuyết chạy mãi mãi), chương trình của bạn vừa bị rò rỉ kích thước không xác định là 255 lần.

Một chương trình tốt, được viết tốt nên luôn luôn dọn dẹp sau đó. Giải phóng tất cả bộ nhớ, xóa tất cả các tệp, đóng tất cả các mô tả, hủy liên kết tất cả các tệp tạm thời, v.v ... Chức năng dọn dẹp này phải đạt được khi chấm dứt bình thường hoặc khi nhận được nhiều loại tín hiệu gây tử vong, trừ khi bạn muốn để lại một số tệp để bạn có thể phát hiện một vụ tai nạn và tiếp tục.

Thực sự, hãy tử tế với linh hồn tội nghiệp, những người phải duy trì công cụ của bạn khi bạn chuyển sang những thứ khác .. đưa nó cho họ 'valgrind sạch' :)


1
Và vâng, tôi đã từng có một đồng đội nói với tôi: "Tôi không bao giờ cần gọi free () trong main ()" <shudders>
Tim Post

free() is cheaptrừ khi bạn có hàng tỷ cấu trúc dữ liệu có mối quan hệ phức tạp mà bạn phải phát hành từng cái một, vượt qua cấu trúc dữ liệu để cố gắng giải phóng mọi thứ có thể sẽ làm tăng đáng kể thời gian tắt của bạn, đặc biệt là nếu một nửa cấu trúc dữ liệu đó đã được phân trang vào đĩa, không có bất kỳ lợi ích.
Lie Ryan

3
@LieRyan Nếu bạn có một tỷ đồng, như trong nghĩa đen một tỷ cấu trúc, bạn decidedly nhất có vấn đề khác mà cần một mức độ chuyên môn xem xét - cách ngoài phạm vi của câu trả lời đặc biệt này :)
Tim Post

13

Hoàn toàn ổn khi để lại bộ nhớ khi bạn thoát; malloc () phân bổ bộ nhớ từ vùng bộ nhớ gọi là "heap" và toàn bộ quá trình được giải phóng khi quá trình thoát.

Điều đó đang được nói, một lý do tại sao mọi người vẫn khăng khăng rằng giải phóng mọi thứ trước khi thoát là vì trình gỡ lỗi bộ nhớ (ví dụ valgrind trên Linux) phát hiện các khối không tham gia khi rò rỉ bộ nhớ và nếu bạn cũng bị rò rỉ bộ nhớ "thực", nó sẽ trở thành khó khăn hơn để phát hiện ra chúng nếu bạn cũng nhận được kết quả "giả" vào cuối.


1
Không Valgrind làm một công việc khá tốt phân biệt giữa "rò rỉ" và "vẫn có thể truy cập"?
Christoffer 17/03/2016

11
-1 cho "hoàn toàn tốt" Đó là thực tế mã hóa xấu để lại bộ nhớ được phân bổ mà không giải phóng nó. Nếu mã đó được trích xuất vào một thư viện, thì nó sẽ gây ra các bản ghi nhớ ở khắp mọi nơi.
DevinB

5
+1 để bù lại. Xem câu trả lời của compie. freetại exitthời điểm được coi là có hại.
R .. GitHub DỪNG GIÚP ICE

11

Nếu bạn đang sử dụng bộ nhớ bạn đã phân bổ, thì bạn không làm gì sai cả. Nó trở thành một vấn đề khi bạn viết các hàm (trừ chính) phân bổ bộ nhớ mà không giải phóng nó, và không có sẵn cho phần còn lại của chương trình. Sau đó, chương trình của bạn tiếp tục chạy với bộ nhớ được phân bổ cho nó, nhưng không có cách nào sử dụng nó. Chương trình của bạn và các chương trình đang chạy khác bị thiếu bộ nhớ đó.

Chỉnh sửa: Không chính xác 100% khi nói rằng các chương trình đang chạy khác bị thiếu bộ nhớ đó. Hệ điều hành luôn có thể cho phép họ sử dụng nó với chi phí hoán đổi chương trình của bạn ra bộ nhớ ảo ( </handwaving>). Tuy nhiên, vấn đề là nếu chương trình của bạn giải phóng bộ nhớ mà nó không sử dụng thì việc hoán đổi bộ nhớ ảo ít có khả năng là cần thiết.


11

Mã này thường sẽ hoạt động ổn, nhưng xem xét vấn đề tái sử dụng mã.

Bạn có thể đã viết một số đoạn mã không có bộ nhớ được cấp phát miễn phí, nó được chạy theo cách mà bộ nhớ sau đó được tự động lấy lại. Có vẻ ổn.

Sau đó, một người khác sao chép đoạn trích của bạn vào dự án của anh ta theo cách nó được thực thi một nghìn lần mỗi giây. Người đó bây giờ có một rò rỉ bộ nhớ lớn trong chương trình của mình. Nói chung là không tốt lắm, thường gây tử vong cho một ứng dụng máy chủ.

Tái sử dụng mã là điển hình trong các doanh nghiệp. Thông thường công ty sở hữu tất cả các mã nhân viên của mình sản xuất và mọi bộ phận có thể sử dụng lại bất cứ thứ gì công ty sở hữu. Vì vậy, bằng cách viết mã "trông ngây thơ" như vậy, bạn sẽ gây ra đau đầu cho người khác. Điều này có thể khiến bạn bị sa thải.


2
Có thể đáng chú ý khả năng không chỉ là ai đó sao chép đoạn trích, mà còn có khả năng một chương trình được viết để thực hiện một số hành động cụ thể một khi được sửa đổi để thực hiện lặp đi lặp lại. Trong trường hợp như vậy, sẽ rất ổn nếu bộ nhớ được cấp phát một lần và sau đó được sử dụng nhiều lần mà không bao giờ được giải phóng, nhưng phân bổ và từ bỏ bộ nhớ cho mọi hành động (không giải phóng nó) có thể là thảm họa.
supercat

7

Kết quả thực sự ở đây là gì?

Chương trình của bạn bị rò rỉ bộ nhớ. Tùy thuộc vào hệ điều hành của bạn, nó có thể đã được phục hồi.

Hầu hết các hệ điều hành máy tính để bàn hiện đại đều phục hồi bộ nhớ bị rò rỉ khi chấm dứt quá trình, khiến cho việc bỏ qua vấn đề này trở nên phổ biến, như nhiều câu trả lời khác có thể thấy ở đây.)

Nhưng bạn đang dựa vào một tính năng an toàn mà bạn không nên dựa vào và chương trình (hoặc chức năng) của bạn có thể chạy trên một hệ thống mà hành vi này dẫn đến rò rỉ bộ nhớ "cứng" vào lần tới .

Bạn có thể đang chạy ở chế độ kernel hoặc trên các hệ điều hành nhúng / cổ điển không sử dụng bảo vệ bộ nhớ như một sự đánh đổi. (MMU chiếm không gian chết, bảo vệ bộ nhớ tốn thêm chu kỳ CPU và không quá nhiều để yêu cầu lập trình viên tự dọn dẹp).

Bạn có thể sử dụng và sử dụng lại bộ nhớ theo bất kỳ cách nào bạn thích, nhưng hãy đảm bảo rằng bạn đã giải phóng tất cả các tài nguyên trước khi thoát.


5

Thực sự có một phần trong sách giáo khoa trực tuyến OSTEP cho một khóa học đại học về các hệ điều hành thảo luận chính xác câu hỏi của bạn.

Phần có liên quan là "Quên bộ nhớ trống" trong phần chương API bộ nhớ ở trang 6, đưa ra lời giải thích sau:

Trong một số trường hợp, có vẻ như không gọi free () là hợp lý. Ví dụ, chương trình của bạn chỉ tồn tại trong thời gian ngắn và sẽ sớm thoát ra; trong trường hợp này, khi quá trình chết, HĐH sẽ dọn sạch tất cả các trang được phân bổ của nó và do đó sẽ không xảy ra rò rỉ bộ nhớ. Trong khi điều này chắc chắn là hoạt động tốt (xem phần bên trên trang 7), có lẽ đó là một thói quen xấu để phát triển, vì vậy hãy cảnh giác khi chọn một chiến lược như vậy

Đoạn trích này nằm trong bối cảnh giới thiệu khái niệm về bộ nhớ ảo. Về cơ bản tại thời điểm này trong cuốn sách, các tác giả giải thích rằng một trong những mục tiêu của hệ điều hành là "ảo hóa bộ nhớ", nghĩa là để mọi chương trình tin rằng nó có quyền truy cập vào một không gian địa chỉ bộ nhớ rất lớn.

Đằng sau hậu trường, hệ điều hành sẽ dịch "địa chỉ ảo" mà người dùng nhìn thấy sang địa chỉ thực tế chỉ vào bộ nhớ vật lý.

Tuy nhiên, việc chia sẻ tài nguyên như bộ nhớ vật lý đòi hỏi hệ điều hành phải theo dõi những quy trình đang sử dụng nó. Vì vậy, nếu một quá trình chấm dứt, thì nó nằm trong khả năng và mục tiêu thiết kế của hệ điều hành để lấy lại bộ nhớ của quy trình để nó có thể phân phối lại và chia sẻ bộ nhớ với các quy trình khác.


EDIT: Các bên được đề cập trong đoạn trích được sao chép dưới đây.

NHƯ VẬY : TẠI SAO KHÔNG CÓ NHỚ NHỚ

Khi bạn viết một chương trình ngắn, bạn có thể phân bổ một số không gian bằng cách sử dụng malloc(). Chương trình chạy và sắp hoàn thành: có cần phải gọi free()một loạt các lần trước khi thoát không? Mặc dù có vẻ không đúng, nhưng không có bộ nhớ sẽ bị "mất" theo bất kỳ ý nghĩa thực sự nào. Lý do rất đơn giản: thực sự có hai cấp quản lý bộ nhớ trong hệ thống. Cấp quản lý bộ nhớ đầu tiên được HĐH thực hiện, giúp xử lý bộ nhớ cho các tiến trình khi chúng chạy và lấy lại khi các tiến trình thoát (hoặc nếu không thì chết). Cấp quản lý thứ hai nằm trong mỗi quy trình, ví dụ như trong heap khi bạn gọi malloc()free(). Ngay cả khi bạn không gọi đượcfree()(và do đó rò rỉ bộ nhớ trong heap), hệ điều hành sẽ lấy lại tất cả bộ nhớ của quá trình (bao gồm cả các trang cho mã, ngăn xếp và, như có liên quan ở đây, heap) khi chương trình chạy xong. Bất kể trạng thái của heap trong không gian địa chỉ của bạn là gì, HĐH sẽ lấy lại tất cả các trang đó khi quá trình chết, do đó đảm bảo rằng không có bộ nhớ bị mất mặc dù thực tế là bạn không giải phóng nó.

Do đó, đối với các chương trình có thời gian sử dụng ngắn, bộ nhớ bị rò rỉ thường không gây ra bất kỳ vấn đề vận hành nào (mặc dù nó có thể được coi là hình thức kém). Khi bạn viết một máy chủ chạy dài (chẳng hạn như máy chủ web hoặc hệ thống quản lý cơ sở dữ liệu không bao giờ thoát), bộ nhớ bị rò rỉ là vấn đề lớn hơn nhiều và cuối cùng sẽ dẫn đến sự cố khi ứng dụng hết bộ nhớ. Và tất nhiên, bộ nhớ bị rò rỉ là một vấn đề thậm chí còn lớn hơn trong một chương trình cụ thể: chính hệ điều hành. Hiển thị cho chúng tôi một lần nữa: những người viết mã hạt nhân có công việc khó khăn nhất trong tất cả ...

từ trang 7 của chương API bộ nhớ của

Hệ điều hành: Ba phần dễ dàng
Remzi H. Arpaci -aleigheau và Andrea C. Arpaci -aleigheau Arpaci -aleigheau Books Tháng 3 năm 2015 (Phiên bản 0,90)


4

Không có nguy hiểm thực sự trong việc không giải phóng các biến của bạn, nhưng nếu bạn gán một con trỏ cho một khối bộ nhớ cho một khối bộ nhớ khác mà không giải phóng khối đầu tiên, thì khối đầu tiên không còn truy cập được mà vẫn chiếm dung lượng. Đây là cái được gọi là rò rỉ bộ nhớ và nếu bạn làm điều này một cách thường xuyên thì quá trình của bạn sẽ bắt đầu tiêu thụ ngày càng nhiều bộ nhớ, lấy đi tài nguyên hệ thống từ các quy trình khác.

Nếu quá trình này diễn ra trong thời gian ngắn, bạn thường có thể thoát khỏi việc này vì tất cả bộ nhớ được phân bổ sẽ được hệ điều hành lấy lại khi quá trình hoàn tất, nhưng tôi khuyên bạn nên tập thói quen giải phóng tất cả bộ nhớ mà bạn không sử dụng nữa.


1
Tôi muốn nói -1 cho câu nói đầu tiên của bạn "không có nguy hiểm" ngoại trừ việc sau đó bạn đưa ra một câu trả lời chu đáo về lý do tại sao có nguy hiểm IS.
DevinB 17/03/2016

2
Khi nguy hiểm xảy ra, nó khá lành tính - Tôi sẽ bị rò rỉ bộ nhớ qua một segfault bất cứ ngày nào.
Kyle Cronin 17/03/2016

1
Rất đúng, và cả hai chúng tôi muốn không phải = D
DevinB

2
@KyleCronin tôi sẽ nhiều hơn có một segfault hơn một rò rỉ bộ nhớ, bởi vì cả hai đều là lỗi nghiêm trọng và segfaults được dễ dàng hơn để phát hiện. Tất cả các rò rỉ bộ nhớ quá thường xuyên không được chú ý hoặc không được giải quyết vì chúng "khá lành tính". RAM của tôi và tôi hoàn toàn không đồng ý.
Dan Bechard

@Dan Là một nhà phát triển, chắc chắn. Là người dùng, tôi sẽ bị rò rỉ bộ nhớ. Tôi muốn có phần mềm hoạt động, mặc dù có bộ nhớ bị rò rỉ, trên phần mềm không có.
Kyle Cronin

3

Bạn hoàn toàn chính xác trong khía cạnh đó. Trong các chương trình nhỏ, trong đó một biến phải tồn tại cho đến khi chương trình chết, không có lợi ích thực sự nào trong việc giải phóng bộ nhớ.

Trên thực tế, tôi đã từng tham gia vào một dự án trong đó mỗi lần thực hiện chương trình rất phức tạp nhưng tương đối ngắn, và quyết định là chỉ giữ bộ nhớ được phân bổ và không làm mất ổn định dự án bằng cách mắc lỗi khi xử lý nó.

Điều đó đang được nói, trong hầu hết các chương trình, đây không thực sự là một lựa chọn, hoặc nó có thể khiến bạn hết bộ nhớ.


2

Bạn đã đúng, bộ nhớ sẽ tự động được giải phóng khi quá trình thoát. Một số người cố gắng không dọn dẹp rộng rãi khi quá trình kết thúc, vì tất cả sẽ được chuyển sang hệ điều hành. Tuy nhiên, trong khi chương trình của bạn đang chạy, bạn nên giải phóng bộ nhớ không sử dụng. Nếu bạn không, cuối cùng bạn có thể hết hoặc gây ra phân trang quá mức nếu bộ công việc của bạn quá lớn.


2

Nếu bạn đang phát triển một ứng dụng từ đầu, bạn có thể đưa ra một số lựa chọn có giáo dục về thời điểm gọi miễn phí. Chương trình ví dụ của bạn là tốt: nó phân bổ bộ nhớ, có thể bạn có nó hoạt động trong vài giây, sau đó đóng lại, giải phóng tất cả các tài nguyên mà nó yêu cầu.

Tuy nhiên, nếu bạn đang viết bất cứ điều gì khác - một máy chủ / ứng dụng chạy dài hoặc thư viện sẽ được người khác sử dụng, bạn nên gọi miễn phí mọi thứ bạn malloc.

Bỏ qua khía cạnh thực dụng trong một giây, sẽ an toàn hơn nhiều nếu làm theo cách tiếp cận chặt chẽ hơn, và buộc bản thân phải giải phóng mọi thứ bạn malloc. Nếu bạn không có thói quen theo dõi rò rỉ bộ nhớ bất cứ khi nào bạn viết mã, bạn có thể dễ dàng tiết lộ một vài rò rỉ. Vì vậy, nói cách khác, có - bạn có thể thoát khỏi mà không cần nó; xin hãy cẩn thận


0

Nếu một chương trình quên giải phóng một vài Megabyte trước khi thoát ra thì hệ điều hành sẽ giải phóng chúng. Nhưng nếu chương trình của bạn chạy hàng tuần liền và một vòng lặp trong chương trình mà quên giải phóng một vài byte trong mỗi lần lặp, bạn sẽ bị rò rỉ bộ nhớ mạnh sẽ ăn hết bộ nhớ có sẵn trong máy tính trừ khi bạn khởi động lại thường xuyên cơ sở => thậm chí rò rỉ bộ nhớ nhỏ có thể là xấu nếu chương trình được sử dụng cho một nhiệm vụ lớn nghiêm trọng ngay cả khi ban đầu nó không được thiết kế cho một.


-2

Tôi nghĩ rằng hai ví dụ của bạn thực sự chỉ là một: điều free()chỉ nên xảy ra ở cuối quá trình, mà như bạn chỉ ra là vô ích vì quá trình đang kết thúc.

Trong ví dụ thứ hai của bạn, sự khác biệt duy nhất là bạn cho phép số lượng không xác định malloc(), điều này có thể dẫn đến hết bộ nhớ. Cách duy nhất để xử lý tình huống là kiểm tra mã trả về malloc()và hành động tương ứng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.