Nếu tôi cần sử dụng một phần bộ nhớ trong suốt tuổi thọ của chương trình của mình, có thực sự cần thiết phải giải phóng nó ngay trước khi kết thúc chương trình không?


67

Trong nhiều cuốn sách và hướng dẫn, tôi đã nghe thực hành quản lý bộ nhớ nhấn mạnh và cảm thấy rằng một số điều bí ẩn và khủng khiếp sẽ xảy ra nếu tôi không giải phóng bộ nhớ sau khi sử dụng xong.

Tôi không thể nói cho các hệ thống khác (mặc dù với tôi là hợp lý khi cho rằng chúng áp dụng một thực tiễn tương tự), nhưng ít nhất trên Windows, Kernel về cơ bản được đảm bảo để dọn sạch hầu hết các tài nguyên (ngoại trừ một số ít) được sử dụng bởi một chương trình sau khi kết thúc chương trình. Trong đó bao gồm bộ nhớ heap, trong số những thứ khác.

Tôi hiểu lý do tại sao bạn muốn đóng tệp sau khi sử dụng xong để cung cấp cho người dùng hoặc tại sao bạn muốn ngắt kết nối ổ cắm được kết nối với máy chủ để tiết kiệm băng thông, nhưng có vẻ ngớ ngẩn phải micromanage TẤT CẢ bộ nhớ của bạn được sử dụng bởi chương trình của bạn.

Bây giờ, tôi đồng ý rằng câu hỏi này rất rộng vì cách bạn xử lý bộ nhớ của bạn dựa trên lượng bộ nhớ bạn cần và khi bạn cần, vì vậy tôi sẽ thu hẹp phạm vi của câu hỏi này: Nếu tôi cần sử dụng một phần của bộ nhớ trong suốt tuổi thọ của chương trình của tôi, có thực sự cần thiết phải giải phóng nó ngay trước khi kết thúc chương trình không?

Chỉnh sửa: Câu hỏi được đề xuất dưới dạng trùng lặp dành riêng cho họ hệ điều hành Unix. Câu trả lời hàng đầu của nó thậm chí đã chỉ định một công cụ dành riêng cho Linux (ví dụ Valgrind). Câu hỏi này có nghĩa là bao gồm hầu hết các hệ điều hành không nhúng "thông thường" và tại sao nó không hoặc không phải là một cách thực hành tốt để giải phóng bộ nhớ cần thiết trong suốt tuổi thọ của chương trình.



19
Bạn đã gắn thẻ ngôn ngữ bất khả tri này nhưng trong nhiều ngôn ngữ (ví dụ java) không cần thiết phải giải phóng bộ nhớ thủ công; nó tự động xảy ra một thời gian sau khi tham chiếu cuối cùng đến một đối tượng vượt ra ngoài phạm vi
Richard Tingle

3
và tất nhiên bạn có thể viết C ++ mà không cần xóa một lần nào và nó sẽ ổn cho bộ nhớ
Nikko

5
@RichardTingle Mặc dù tôi không thể nghĩ ra bất kỳ ngôn ngữ nào khác ngoài đầu của mình ngoài C và có thể là C ++, thẻ không biết ngôn ngữ có nghĩa là bao gồm tất cả các ngôn ngữ không có nhiều tiện ích thu gom rác tích hợp. Điều đó được thừa nhận ngày nay hiếm. Bạn có thể lập luận rằng bây giờ bạn có thể thực hiện một hệ thống như vậy trong C ++, nhưng cuối cùng bạn vẫn có lựa chọn không xóa một phần bộ nhớ.
CaptainObingly

4
Bạn viết: "Hạt nhân về cơ bản được đảm bảo để dọn sạch tất cả các tài nguyên [...] sau khi kết thúc chương trình". Điều này nói chung là sai, vì không phải tất cả mọi thứ là bộ nhớ, tay cầm hoặc đối tượng kernel. Nhưng nó đúng với bộ nhớ mà bạn ngay lập tức hạn chế câu hỏi của mình.
Tháp Eric

Câu trả lời:


108

Nếu tôi cần sử dụng một phần bộ nhớ trong suốt tuổi thọ của chương trình của mình, có thực sự cần thiết phải giải phóng nó ngay trước khi kết thúc chương trình không?

Nó không bắt buộc, nhưng nó có thể có lợi ích (cũng như một số nhược điểm).

Nếu chương trình cấp phát bộ nhớ một lần trong thời gian thực hiện và nếu không sẽ không bao giờ giải phóng bộ nhớ cho đến khi quá trình kết thúc, đó có thể là một cách tiếp cận hợp lý để không giải phóng bộ nhớ theo cách thủ công và dựa vào HĐH. Trên mọi hệ điều hành hiện đại mà tôi biết, điều này là an toàn, vào cuối quá trình, tất cả bộ nhớ được phân bổ sẽ được trả lại đáng tin cậy cho hệ thống.
Trong một số trường hợp, việc không dọn dẹp bộ nhớ được phân bổ rõ ràng thậm chí có thể nhanh hơn đáng kể so với việc dọn dẹp.

Tuy nhiên, bằng cách giải phóng tất cả bộ nhớ khi kết thúc thực hiện một cách rõ ràng,

  • trong quá trình gỡ lỗi / kiểm tra, các công cụ phát hiện rò rỉ mem sẽ không hiển thị cho bạn "dương tính giả"
  • Có thể dễ dàng hơn để di chuyển mã sử dụng bộ nhớ cùng với phân bổ và phân bổ thành một thành phần riêng biệt và sử dụng nó sau trong một bối cảnh khác trong đó thời gian sử dụng cho bộ nhớ cần được kiểm soát bởi người dùng thành phần

Tuổi thọ của các chương trình có thể thay đổi. Có thể chương trình của bạn là một tiện ích dòng lệnh nhỏ ngày nay, với thời gian sử dụng dưới 10 phút thông thường và nó phân bổ bộ nhớ theo các phần của kb mỗi 10 giây - vì vậy không cần giải phóng bất kỳ bộ nhớ được phân bổ nào trước khi chương trình kết thúc. Sau đó, chương trình được thay đổi và được sử dụng mở rộng như một phần của quy trình máy chủ có thời gian tồn tại vài tuần - vì vậy, không giải phóng bộ nhớ không sử dụng ở giữa không phải là một tùy chọn nữa, nếu không chương trình của bạn bắt đầu ăn hết bộ nhớ máy chủ có sẵn theo thời gian . Điều này có nghĩa là bạn sẽ phải xem lại toàn bộ chương trình và thêm mã giải quyết sau đó. Nếu bạn may mắn, đây là một nhiệm vụ dễ dàng, nếu không, nó có thể khó đến mức rất có thể bạn sẽ bỏ lỡ một vị trí. Và khi bạn ở trong tình huống đó, bạn sẽ ước mình đã thêm "miễn phí"

Tổng quát hơn, việc viết mã phân bổ và mã giải quyết liên quan luôn được tính theo cặp là một "thói quen tốt" trong số nhiều lập trình viên: bằng cách này luôn luôn, bạn giảm xác suất quên mã giải quyết trong các tình huống phải giải phóng bộ nhớ .


12
Điểm tốt về phát triển thói quen lập trình tốt.
Lawrence

4
Nói chung tôi đồng ý mạnh mẽ với lời khuyên này. Giữ thói quen tốt với trí nhớ là rất quan trọng. Tuy nhiên, tôi nghĩ có những ngoại lệ. Ví dụ: nếu những gì bạn đã phân bổ là một số biểu đồ điên rồ sẽ mất vài giây để duyệt và "miễn phí", thì bạn sẽ cải thiện trải nghiệm người dùng bằng cách kết thúc chương trình và để HĐH khởi động nó.
GrandOpener

1
Nó an toàn trên mọi HĐH "bình thường" (không nhúng với bảo vệ bộ nhớ) hiện đại và sẽ là một lỗi nghiêm trọng nếu không có. Nếu một quá trình không có đặc quyền có thể mất bộ nhớ vĩnh viễn mà HĐH sẽ không lấy lại được, thì việc chạy nó nhiều lần có thể khiến hệ thống hết bộ nhớ. Sức khỏe lâu dài của HĐH không thể phụ thuộc vào việc thiếu lỗi trong các chương trình không có đặc quyền. (tất nhiên, điều này là bỏ qua những thứ như phân đoạn bộ nhớ chia sẻ Unix, vốn chỉ được hỗ trợ bởi không gian hoán đổi tốt nhất.)
Peter Cordes

7
@GrandOpener Trong trường hợp này, bạn có thể thích sử dụng một số loại phân bổ theo vùng cho cây này, để bạn có thể phân bổ nó theo cách thông thường và chỉ phân bổ toàn bộ khu vực cùng một lúc khi đến lúc, thay vì đi ngang qua nó nó từng chút một Điều đó vẫn "đúng".
Thomas

3
Ngoài ra, nếu bộ nhớ thực sự cần tồn tại trong toàn bộ thời gian của chương trình, thì đó có thể là một sự thay thế hợp lý để tạo cấu trúc trên ngăn xếp, ví dụ như trong main.
Kyle Strand

11

Giải phóng bộ nhớ khi kết thúc chương trình chạy chỉ là lãng phí thời gian của CPU. Nó giống như dọn dẹp một ngôi nhà trước khi lấy nó ra khỏi quỹ đạo.

Tuy nhiên, đôi khi một chương trình chạy ngắn có thể biến thành một phần của chương trình chạy dài hơn nhiều. Sau đó giải phóng công cụ trở nên cần thiết. Nếu điều này không được nghĩ đến ít nhất là ở một mức độ nào đó thì nó có thể liên quan đến việc làm lại đáng kể.

Một giải pháp thông minh cho vấn đề này là "Talloc" cho phép bạn thực hiện tải phân bổ bộ nhớ và sau đó ném tất cả chúng đi với một cuộc gọi.


1
Giả sử bạn biết hệ điều hành của bạn không có rò rỉ riêng.
WGroleau

5
"lãng phí thời gian của CPU" Mặc dù một lượng thời gian rất nhỏ. freethường nhanh hơn nhiều malloc.
Paul Draper

@PaulDraper Aye, lãng phí thời gian hoán đổi mọi thứ trở lại có ý nghĩa hơn nhiều.
Ded repeatator

5

Bạn có thể sử dụng một ngôn ngữ với bộ sưu tập rác (như Scheme, Ocaml, Haskell, Common Lisp và thậm chí Java, Scala, Clojure).

(Trong hầu hết các ngôn ngữ GC-ed, có không có cách nào để một cách rõ ràngbằng tay miễn phí bộ nhớ Đôi khi, một số giá trị có thể được! Hoàn thiện , ví dụ như GC và hệ thống runtime sẽ đóng một tập tin xử lý giá trị khi giá trị đó là không thể truy cập, nhưng đây không phải là chắc chắn, không đáng tin cậy và thay vào đó bạn nên đóng rõ ràng xử lý tệp của mình, vì quyết toán không bao giờ được đảm bảo)

Bạn cũng có thể, đối với chương trình của bạn được mã hóa bằng C (hoặc thậm chí là C ++), hãy sử dụng trình thu gom rác bảo thủ của Boehm . Sau đó, bạn sẽ thay thế tất cả mallocbằng GC_malloc và không bận tâm về việc sử dụng freebất kỳ con trỏ nào. Tất nhiên bạn cần hiểu những ưu và nhược điểm của việc sử dụng GC của Boehm. Đọc thêm cẩm nang GC .

Quản lý bộ nhớ là một tài sản toàn cầu của một chương trình. Theo một cách nào đó, nó (và tính sinh động của một số dữ liệu nhất định) là không cấu thành và không mô đun, vì toàn bộ thuộc tính chương trình.

Cuối cùng, như những người khác đã chỉ ra, rõ ràng - freeviệc sử dụng vùng nhớ C được phân bổ heap của bạn là một thực hành tốt. Đối với một chương trình đồ chơi không phân bổ nhiều bộ nhớ, bạn thậm chí có thể quyết định không sử dụng freebộ nhớ (vì khi quá trình kết thúc, tài nguyên của nó, bao gồm cả không gian địa chỉ ảo , sẽ được hệ điều hành giải phóng).

Nếu tôi cần sử dụng một phần bộ nhớ trong suốt tuổi thọ của chương trình của mình, có thực sự cần thiết phải giải phóng nó ngay trước khi kết thúc chương trình không?

Không, bạn không cần phải làm điều đó. Và nhiều chương trình trong thế giới thực không bận tâm giải phóng một số bộ nhớ cần thiết cho toàn bộ tuổi thọ (đặc biệt là trình biên dịch GCC không giải phóng một số bộ nhớ của nó). Tuy nhiên, khi bạn làm điều đó (ví dụ: bạn không bận tâm đến freemột số dữ liệu C được phân bổ động ), bạn sẽ nhận xét tốt hơn thực tế đó để giảm bớt công việc của các lập trình viên tương lai trong cùng một dự án. Tôi có xu hướng khuyến nghị rằng số lượng bộ nhớ không liên kết vẫn bị giới hạn và thường là tương đối nhỏ so với tổng bộ nhớ heap đã sử dụng.

Lưu ý rằng hệ thống freethường không giải phóng bộ nhớ cho HĐH (ví dụ: bằng cách gọi munmap (2) trên các hệ thống POSIX) nhưng thường đánh dấu vùng nhớ là có thể tái sử dụng trong tương lai malloc. Cụ thể, không gian địa chỉ ảo (ví dụ như đã thấy /proc/self/mapstrên Linux, xem Proc (5) ....) có thể không bị thu hẹp sau free(do đó các tiện ích như pshoặc topđang báo cáo cùng một lượng bộ nhớ đã sử dụng cho quy trình của bạn).


3
"Đôi khi, một số giá trị có thể được hoàn thành, ví dụ: hệ thống thời gian chạy và hệ thống thời gian chạy sẽ đóng giá trị xử lý tệp khi giá trị đó không thể truy cập được" bất kỳ trình hoàn thiện nào đã từng chạy, ngay cả khi thoát: stackoverflow.com/questions/7880569/ cấp
Ded repeatator

Cá nhân tôi thích câu trả lời này, vì nó khuyến khích chúng tôi sử dụng GC (tức là cách tiếp cận tự động hơn).
Tạ Thành Đình

3
@tathanhdinh Tự động hơn, mặc dù chỉ dành cho bộ nhớ. Hoàn toàn thủ công, cho tất cả các tài nguyên khác. GC hoạt động bằng cách xác định giao dịch và bộ nhớ để xử lý bộ nhớ thuận tiện. Và không, người hoàn thiện không giúp được gì nhiều, và có vấn đề của riêng họ.
Ded repeatator

3

Điều đó là không cần thiết , vì trong bạn sẽ không thực hiện đúng chương trình của mình nếu bạn thất bại. Tuy nhiên, có những lý do bạn có thể chọn, nếu có cơ hội.

Một trong những trường hợp mạnh nhất mà tôi gặp phải (hết lần này đến lần khác) là ai đó viết một đoạn mã mô phỏng nhỏ chạy trong tệp thực thi của họ. Họ nói rằng "chúng tôi muốn mã này được tích hợp vào mô phỏng." Sau đó tôi hỏi họ làm thế nào họ dự định khởi tạo lại giữa các lần chạy monte-carlo và họ nhìn tôi thất thần. "Ý bạn là gì để khởi tạo lại? Bạn chỉ cần chạy chương trình với các cài đặt mới?"

Đôi khi việc dọn dẹp sạch sẽ giúp phần mềm của bạn được sử dụng dễ dàng hơn nhiều . Trong trường hợp có nhiều ví dụ, bạn cho rằng bạn không bao giờ cần phải dọn dẹp thứ gì đó và bạn đưa ra các giả định về cách bạn có thể xử lý dữ liệu và tuổi thọ của nó xung quanh các giả định đó. Khi bạn chuyển đến một môi trường mới nơi những giả định đó không hợp lệ, toàn bộ thuật toán có thể không còn hoạt động.

Để biết ví dụ về cách những thứ lạ có thể nhận được, hãy xem cách các ngôn ngữ được quản lý xử lý quyết toán ở cuối quy trình hoặc cách C # xử lý việc tạm dừng lập trình của Miền ứng dụng. Họ tự trói mình lại với nhau vì có những giả định rơi vào vết nứt trong những trường hợp cực đoan này.


1

Bỏ qua các ngôn ngữ mà bạn không giải phóng bộ nhớ theo cách thủ công nào ...

Những gì bạn nghĩ là "một chương trình" ngay bây giờ đôi khi có thể trở thành một chức năng hoặc phương thức là một phần của một chương trình lớn hơn. Và sau đó chức năng đó có thể được gọi nhiều lần. Và sau đó bộ nhớ mà bạn nên "giải phóng thủ công" sẽ bị rò rỉ bộ nhớ. Đó tất nhiên là một cuộc gọi phán xét.


2
điều này dường như chỉ lặp lại điểm được thực hiện (và được giải thích tốt hơn nhiều) trong câu trả lời hàng đầu đã được đăng vài giờ trước đó: "việc di chuyển mã sử dụng bộ nhớ cùng với phân bổ và phân bổ vào một thành phần riêng biệt sẽ dễ dàng hơn nhiều trong một bối cảnh khác trong đó thời gian sử dụng cho bộ nhớ cần được kiểm soát bởi người dùng thành phần ... "
gnat

1

Bởi vì rất có thể trong một lúc nào đó, bạn sẽ muốn thay đổi chương trình của mình, có thể tích hợp nó với một thứ khác, chạy một số trường hợp theo trình tự hoặc song song. Sau đó, sẽ trở nên cần thiết để giải phóng bộ nhớ này theo cách thủ công - nhưng bạn sẽ không còn nhớ các tình huống nữa và bạn sẽ mất nhiều thời gian hơn để hiểu lại chương trình của mình.

Làm những điều trong khi sự hiểu biết của bạn về chúng vẫn còn mới.

Đó là một khoản đầu tư nhỏ có thể mang lại lợi nhuận lớn trong tương lai.


2
điều này dường như không thêm bất cứ điều gì đáng kể vào các điểm được thực hiện và giải thích trong 11 câu trả lời trước. Cụ thể, điểm về khả năng thay đổi chương trình trong tương lai đã được thực hiện ba hoặc bốn lần
gnat

1
@gnat Theo cách phức tạp và bị che khuất - có. Trong một tuyên bố rõ ràng - không. Hãy tập trung vào chất lượng câu trả lời, không phải tốc độ.
Đặc vụ_L

1
chất lượng khôn ngoan, câu trả lời hàng đầu dường như tốt hơn nhiều trong việc giải thích điểm này
gnat

1

Không, nó không cần thiết, nhưng đó là một ý tưởng tốt.

Bạn nói rằng bạn cảm thấy rằng "những điều bí ẩn và khủng khiếp sẽ xảy ra nếu tôi không giải phóng bộ nhớ sau khi sử dụng xong."

Về mặt kỹ thuật, hậu quả duy nhất của việc này là chương trình của bạn sẽ tiếp tục ăn nhiều bộ nhớ hơn cho đến khi đạt đến giới hạn cứng (ví dụ: không gian địa chỉ ảo của bạn đã hết) hoặc hiệu suất không được chấp nhận. Nếu chương trình sắp thoát ra, không có vấn đề nào trong số này vì quá trình này thực sự không còn tồn tại. "Những điều bí ẩn và khủng khiếp" hoàn toàn là về trạng thái tinh thần của nhà phát triển. Tìm ra nguồn rò rỉ bộ nhớ có thể là một cơn ác mộng tuyệt đối (đây là một cách nói nhỏ) và cần rất nhiều kỹ năng và kỷ luật để viết mã không bị rò rỉ. Cách tiếp cận được đề xuất để phát triển kỹ năng và kỷ luật này là luôn giải phóng bộ nhớ một khi không còn cần thiết, ngay cả khi chương trình sắp kết thúc.

Tất nhiên điều này có thêm lợi thế là mã của bạn có thể được sử dụng lại và điều chỉnh dễ dàng hơn, như những người khác đã nói.

Tuy nhiên , có ít nhất một trường hợp tốt hơn là không giải phóng bộ nhớ ngay trước khi kết thúc chương trình.

Hãy xem xét trường hợp bạn đã thực hiện hàng triệu phân bổ nhỏ và chúng hầu hết đã được hoán đổi vào đĩa. Khi bạn bắt đầu giải phóng mọi thứ, hầu hết bộ nhớ của bạn cần được trao đổi trở lại vào RAM để có thể truy cập thông tin sổ sách, chỉ để loại bỏ ngay dữ liệu. Điều này có thể làm cho chương trình mất vài phút chỉ để thoát! Chưa kể gây áp lực lớn lên đĩa và bộ nhớ vật lý trong thời gian đó. Nếu bộ nhớ vật lý bị thiếu hụt để bắt đầu (có lẽ chương trình đang bị đóng do chương trình khác đang nhai rất nhiều bộ nhớ) thì các trang riêng lẻ có thể cần được hoán đổi nhiều lần khi một số đối tượng cần được giải phóng khỏi cùng một trang, nhưng không được giải phóng liên tiếp.

Nếu thay vào đó, chương trình chỉ hủy bỏ, HĐH sẽ đơn giản loại bỏ tất cả bộ nhớ đã bị tráo đổi sang đĩa, điều này gần như tức thời vì nó không yêu cầu bất kỳ quyền truy cập đĩa nào.

Điều quan trọng cần lưu ý là trong ngôn ngữ OO, việc gọi hàm hủy của đối tượng cũng sẽ buộc bộ nhớ bị hoán đổi; nếu bạn phải làm điều này, bạn cũng có thể giải phóng bộ nhớ.


1
Bạn có thể trích dẫn một cái gì đó để hỗ trợ cho ý tưởng rằng bộ nhớ phân trang cần phải được phân trang để giải quyết? Đó rõ ràng là không hiệu quả, chắc chắn các hệ điều hành hiện đại thông minh hơn thế!

1
@Jon of All Trades, HĐH hiện đại là thông minh, nhưng trớ trêu thay hầu hết các ngôn ngữ hiện đại thì không. Khi bạn rảnh (thứ gì đó), trình biên dịch sẽ đặt "cái gì đó" vào ngăn xếp, sau đó gọi free (). Đặt nó vào ngăn xếp yêu cầu đổi nó trở lại vào RAM nếu nó bị tráo đổi.
Jeffiekins

@Jonof ALLTrades một danh sách miễn phí sẽ có tác dụng đó, đó là một triển khai malloc khá lỗi thời. Nhưng HĐH không thể ngăn bạn sử dụng cách triển khai như vậy.

0

Các lý do chính để dọn dẹp thủ công là: bạn ít có khả năng nhầm lẫn rò rỉ bộ nhớ chính hãng cho khối bộ nhớ sẽ bị xóa khi thoát, đôi khi bạn sẽ chỉ gặp lỗi trong bộ cấp phát khi bạn đi bộ toàn bộ cấu trúc dữ liệu deallocate nó, và bạn sẽ phải đau đầu nhỏ hơn nhiều nếu bạn đã từng cấu trúc lại để một số đối tượng không cần phải nhận được deallocated trước khi thoát khỏi chương trình.

Những lý do chính để không dọn dẹp thủ công là: hiệu suất, hiệu suất, hiệu suất và khả năng xảy ra một số lỗi miễn phí hai lần hoặc sau khi sử dụng miễn phí trong mã dọn dẹp không cần thiết của bạn, có thể biến thành lỗi sự cố hoặc bảo mật khai thác.

Nếu bạn quan tâm đến hiệu suất, bạn luôn muốn lập hồ sơ để tìm ra nơi bạn lãng phí toàn bộ thời gian của mình, thay vì đoán. Một thỏa hiệp có thể là bọc mã dọn dẹp tùy chọn của bạn trong một khối có điều kiện, để lại mã khi gỡ lỗi để bạn nhận được lợi ích của việc viết và gỡ lỗi mã, và sau đó, nếu và chỉ khi bạn xác định theo kinh nghiệm thì nó quá nhiều Chi phí hoạt động, báo cho trình biên dịch bỏ qua nó trong tệp thực thi cuối cùng của bạn. Và một sự chậm trễ trong việc đóng chương trình, gần như theo định nghĩa, hầu như không bao giờ trong con đường quan trọ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.