Rò rỉ bộ nhớ có thể đi bao xa?


118

Tôi đã gặp phải tình trạng rò rỉ bộ nhớ nhiều lần. Thường thì khi tôi nghĩ mallocnhư không có ngày mai, hoặc lủng lẳng FILE *như đồ giặt bẩn. Tôi thường giả định (đọc: hy vọng tuyệt vọng) rằng tất cả bộ nhớ được dọn dẹp ít nhất khi chương trình kết thúc. Có bất kỳ tình huống nào mà bộ nhớ bị rò rỉ sẽ không được thu thập khi chương trình kết thúc hoặc bị treo không?

Nếu câu trả lời rất khác nhau giữa các ngôn ngữ, thì hãy tập trung vào C (++).

Xin lưu ý cách sử dụng hypebol của cụm từ, 'như không có ngày mai' và 'lủng lẳng ... như đồ giặt bẩn'. * malloc* Ing không an toàn có thể làm tổn thương những người bạn yêu thương. Ngoài ra, hãy cẩn thận với đồ giặt bẩn.


3
Nếu bạn đang chạy với một hệ điều hành "hiện đại" như Linux hoặc Windows, thì bản thân hệ điều hành đó sẽ giải quyết mọi bộ nhớ chưa được phát hành khi chương trình kết thúc.
Oliver Charlesworth 17/03/13

60
Thay vì suy nghĩ mông lung như không có ngày mai, hãy thử giả vờ có ngày mai và theo dõi trí nhớ của bạn!
William Pursell

8
@WilliamPursell à, vậy là bạn đang nói một người nên callocnhư không có ngày mai. Thông minh.
DilithiumMatrix

8
"Nếu câu trả lời rất khác nhau giữa các ngôn ngữ, thì hãy tập trung vào c (++)." cc ++ không phải là cùng một ngôn ngữ!
Johnsyweb

11
@zhermes: Nhận xét về việc C và C ++ là các ngôn ngữ khác nhau ẩn chứa nhiều thứ hơn bạn nghĩ ... Trong C ++, bạn sẽ thấy mình tận dụng các đối tượng có thời lượng lưu trữ tự động, theo thành ngữ RAII ... bạn để các đối tượng này chăm sóc bộ nhớ quản lý cho bạn.
LihO

Câu trả lời:


111

Không. Hệ điều hành giải phóng tất cả tài nguyên do các quy trình nắm giữ khi chúng thoát ra.

Điều này áp dụng cho tất cả các tài nguyên mà hệ điều hành duy trì: bộ nhớ, tệp đang mở, kết nối mạng, tay cầm cửa sổ ...

Điều đó nói rằng, nếu chương trình đang chạy trên hệ thống nhúng không có hệ điều hành hoặc với hệ điều hành rất đơn giản hoặc có lỗi, bộ nhớ có thể không sử dụng được cho đến khi khởi động lại. Nhưng nếu bạn ở trong tình huống đó, bạn có thể sẽ không hỏi câu hỏi này.

Hệ điều hành có thể mất nhiều thời gian để giải phóng một số tài nguyên nhất định. Ví dụ: cổng TCP mà máy chủ mạng sử dụng để chấp nhận kết nối có thể mất vài phút để trở nên rảnh, ngay cả khi chương trình đã đóng đúng cách. Một chương trình được nối mạng cũng có thể chứa các tài nguyên từ xa như các đối tượng cơ sở dữ liệu. Hệ thống từ xa sẽ giải phóng các tài nguyên đó khi mất kết nối mạng, nhưng có thể mất nhiều thời gian hơn hệ điều hành cục bộ.


5
Một mô hình phổ biến trong RTOS là mô hình một quy trình, nhiều luồng và không có bảo vệ bộ nhớ giữa các 'tác vụ'. Thường có một đống. Đây chắc chắn là cách VxWorks từng hoạt động - và có lẽ vẫn còn.
marko

29
Lưu ý rằng không phải tất cả tài nguyên đều có thể được giải phóng bởi hệ điều hành. Kết nối mạng, giao dịch cơ sở dữ liệu, v.v., không đóng chúng một cách rõ ràng có thể gây ra một số kết quả không mong muốn. Việc không đóng kết nối mạng có thể khiến máy chủ nghĩ rằng bạn vẫn đang hoạt động trong một khoảng thời gian không xác định và đối với các máy chủ giới hạn số lượng kết nối đang hoạt động, có thể vô tình gây ra từ chối dịch vụ. Không đóng các giao dịch cơ sở dữ liệu có thể khiến bạn mất dữ liệu chưa được cam kết.
Lie Ryan

1
@Marko: Phiên bản vxWorks gần đây hiện hỗ trợ RTP (quy trình thời gian thực) hỗ trợ bảo vệ bộ nhớ.
Xavier T.

20
"Hệ điều hành giải phóng tất cả tài nguyên do các quy trình nắm giữ khi chúng thoát ra." Không hoàn toàn đúng. Ví dụ: trên (ít nhất) các semaphores Linux, SysV và các đối tượng IPC khác không được dọn dẹp khi thoát quy trình. Đó là lý do tại sao có tính năng ipcrmdọn dẹp thủ công, linux.die.net/man/8/ipcrm .
sleske

7
Ngoài ra, nếu một đối tượng có tệp tạm thời mà nó duy trì, tệp đó rõ ràng sẽ không được dọn dẹp sau đó.
Mooing Duck,

47

Tiêu chuẩn C không chỉ định rằng bộ nhớ được cấp phát mallocđược giải phóng khi chương trình kết thúc. Điều này được thực hiện bởi hệ điều hành và không phải tất cả các hệ điều hành (thường là trong thế giới nhúng) giải phóng bộ nhớ khi chương trình kết thúc.


20
Đó là ít nhiều bởi vì tiêu chuẩn C nói về các chương trình C, không phải hệ điều hành mà C sẽ chạy trên đó ...
vonbrand

5
@vonbrand Tiêu chuẩn C có thể có một đoạn nói rằng khi maintrả về tất cả bộ nhớ được cấp phát mallocđược giải phóng. Ví dụ, nó nói rằng tất cả các tệp đang mở đều bị đóng trước khi kết thúc chương trình. Đối với bộ nhớ được cấp phát của tôi malloc, nó chỉ là không được chỉ định. Tất nhiên bây giờ câu của tôi liên quan đến OS mô tả những gì thường được thực hiện không phải những gì Tiêu chuẩn quy định, vì nó không chỉ định bất kỳ điều gì về điều này.
ouah

Hãy để tôi sửa lại nhận xét của tôi: Tiêu chuẩn nói về C, không phải về cách chương trình được bắt đầu và dừng lại. Bạn rất có thể viết một chương trình C chạy mà không cần hệ điều hành. Trong trường hợp đó sẽ không có ai dọn dẹp. Tiêu chuẩn rất cố tình không chỉ định bất cứ điều gì trừ khi cần thiết, để không hạn chế việc sử dụng mà không cần thiết.
vonbrand

2
@ouah: " khi main trả về ...". Đó là một giả định. Chúng ta phải xem xét " nếu main trả về ...". std::atexitcũng xem xét kết thúc chương trình thông qua std::exit, và sau đó cũng có std::abortvà (C ++ cụ thể) std::terminate.
MSalters

@ouah: Nếu cái đó đã được bao gồm, atexitsẽ không thể sử dụng được. :-)
R .. GitHub DỪNG TRỢ GIÚP ICE

28

Vì tất cả các câu trả lời đã bao gồm hầu hết các khía cạnh của câu hỏi của bạn đối với hệ điều hành hiện đại, nhưng về mặt lịch sử, có một điều đáng nói nếu bạn đã từng lập trình trong thế giới DOS. Các chương trình Terminant và Stay Resident (TSR) thường sẽ trả lại quyền điều khiển cho hệ thống nhưng sẽ nằm trong bộ nhớ có thể được hồi sinh bởi một ngắt phần mềm / phần cứng. Thông thường bạn thấy các thông báo như "hết bộ nhớ! Hãy thử tải một số TSR của bạn" khi làm việc trên các hệ điều hành này.

Vì vậy, về mặt kỹ thuật, chương trình sẽ kết thúc , nhưng vì nó vẫn nằm trên bộ nhớ, bất kỳ rò rỉ bộ nhớ nào sẽ không được giải phóng trừ khi bạn dỡ bỏ chương trình.

Vì vậy, bạn có thể coi đây là một trường hợp khác ngoài việc hệ điều hành không lấy lại bộ nhớ vì lỗi hoặc do hệ điều hành nhúng được thiết kế để làm như vậy.

Tôi nhớ một ví dụ nữa. Hệ thống kiểm soát thông tin khách hàng (CICS), một máy chủ giao dịch chạy chủ yếu trên các máy tính lớn của IBM là giả đàm thoại. Khi được thực thi, nó sẽ xử lý dữ liệu do người dùng nhập vào, tạo ra một tập hợp dữ liệu khác cho người dùng, chuyển đến nút đầu cuối của người dùng và kết thúc. Khi kích hoạt khóa chú ý, nó lại hồi sinh để xử lý một bộ dữ liệu khác. Bởi vì cách nó hoạt động, một lần nữa về mặt kỹ thuật, Hệ điều hành sẽ không lấy lại bộ nhớ từ các Chương trình CICS đã chấm dứt, trừ khi bạn tái chế máy chủ giao dịch CICS.


Điều đó thực sự thú vị, cảm ơn vì ghi chú lịch sử! Bạn có biết liệu mô hình đó có phải do việc giải phóng bộ nhớ quá tốn kém về mặt tính toán nếu không cần thiết không? Hay bạn chưa từng nghĩ đến giải pháp thay thế?
DilithiumMatrix

1
@zhermes: Về mặt tính toán là không thể, vì DOS đơn giản là không theo dõi phân bổ bộ nhớ cho TSR. Theo định nghĩa thì khá nhiều: mục tiêu là Ở lại thường trú . Nếu bạn muốn TSR của mình giải phóng một số nhưng không phải tất cả bộ nhớ, bạn sẽ quyết định giải phóng cái gì.
MSalters

2
@zhermes: DOS (như CP / M, cha đẻ của nó) không phải là thứ mà bạn gọi là hệ điều hành theo nghĩa hiện đại. Nó thực sự chỉ là một tập hợp các tiện ích I / O có thể được gọi theo cách tiêu chuẩn đi kèm với một bộ xử lý lệnh cho phép bạn chạy một chương trình tại một thời điểm. Không có khái niệm về các tiến trình, và bộ nhớ không ảo cũng không được bảo vệ. TSR là một bản hack hữu ích có thể cho hệ thống biết rằng chúng đang chiếm tới 64K dung lượng và sẽ tự kết nối với các ngắt để chúng được gọi.
Blrfl

8

Giống như những người khác đã nói, hầu hết các hệ điều hành sẽ lấy lại bộ nhớ được cấp phát khi kết thúc quá trình (và có thể là các tài nguyên khác như ổ cắm mạng, trình xử lý tệp, v.v.).

Phải nói rằng, bộ nhớ có thể không phải là thứ duy nhất bạn cần phải lo lắng khi xử lý new / delete (thay vì raw malloc / free). Bộ nhớ được cấp mới có thể được lấy lại, nhưng những điều có thể được thực hiện trong bộ hủy của các đối tượng sẽ không xảy ra. Có lẽ trình hủy của một lớp nào đó ghi giá trị sentinel vào một tệp khi bị hủy. Nếu quá trình vừa kết thúc, trình xử lý tệp có thể bị xóa và bộ nhớ được lấy lại, nhưng giá trị sentinel đó sẽ không được ghi.

Đạo đức của câu chuyện, luôn luôn làm sạch sau khi chính mình. Đừng để mọi thứ lủng lẳng. Đừng dựa vào hệ điều hành dọn dẹp sau khi bạn. Dọn dẹp sau khi chính bạn.


'Đừng dựa vào hệ điều hành đang dọn dẹp sau khi bạn. Hãy dọn dẹp sau khi chính mình. ' Điều này thường là ... 'rất, rất khó' với các ứng dụng đa luồng phức tạp. Rò rỉ thực tế, trong đó tất cả các tham chiếu đến tài nguyên đã bị mất, là xấu. Cho phép hệ điều hành dọn dẹp thay vì giải phóng rõ ràng các tham chiếu không phải lúc nào cũng xấu và thường là cách hợp lý duy nhất để thực hiện.
Martin James

1
Trong C ++, hàm hủy sẽ được gọi về việc chấm dứt chương trình (trừ một số ít hơn sáng kill -9show quạt lên ...)
vonbrand

@vonbrand Đúng, nhưng nếu chúng ta đang nói về rò rỉ với các đối tượng động, thì những bộ hủy đó sẽ không xảy ra. Đối tượng đi ra ngoài phạm vi là một con trỏ thô, và trình hủy của nó là một lệnh cấm. (Tất nhiên, hãy xem các đối tượng RAII để giảm thiểu vấn đề này ...)
Andre Kostur

1
Vấn đề với RAII là nó khăng khăng yêu cầu phân bổ các đối tượng khi thoát quy trình mà việc loại bỏ nó không thực sự quan trọng. Các kết nối DB mà bạn muốn cẩn thận, nhưng bộ nhớ chung được hệ điều hành dọn dẹp tốt nhất (nó hoạt động tốt hơn nhiều). Vấn đề thể hiện ở chỗ một chương trình mất độ tuổi hoàn toàn để thoát khi dung lượng bộ nhớ được phân trang tăng lên. Nó cũng không tầm thường để giải quyết…
Donal Fellows

@vonbrand: Không đơn giản đâu. std::exitsẽ gọi dtors, std::abortsẽ không, có thể có các ngoại lệ chưa được suy nghĩ.
MSalters

7

Điều này có nhiều khả năng phụ thuộc vào hệ điều hành hơn là ngôn ngữ. Cuối cùng thì bất kỳ chương trình nào bằng bất kỳ ngôn ngữ nào cũng sẽ nhận được bộ nhớ của nó từ hệ điều hành.

Tôi chưa bao giờ nghe nói về hệ điều hành không tái chế bộ nhớ khi một chương trình thoát / gặp sự cố. Vì vậy, nếu chương trình của bạn có giới hạn trên trên bộ nhớ mà nó cần cấp phát, thì việc chỉ cấp phát và không bao giờ giải phóng là hoàn toàn hợp lý.


Bạn có thể sửa đổi hình ảnh bộ nhớ của hạt nhân trong trường hợp một hệ điều hành đơn giản không? .. Giống như, những hệ điều hành đó thậm chí không có đa nhiệm.
ulidtko

@ulidtko, điều này sẽ làm hỏng mọi thứ. Nếu chương trình của tôi yêu cầu nói 1GiB thỉnh thoảng và lấy nó trong thời gian dài, nó đang từ chối sử dụng các tài nguyên đó cho người khác ngay cả khi không sử dụng nó. Điều đó có thể quan trọng ngày hôm nay, hoặc không. Nhưng môi trường sẽ thay đổi hoàn toàn. Đảm bảo.
vonbrand

@vonbrand Việc hiếm khi sử dụng 1GiB không phải là vấn đề bình thường (miễn là bạn có nhiều bộ nhớ vật lý) vì các hệ điều hành hiện đại có thể trang ra các bit hiện không hoạt động. Vấn đề xảy ra khi bạn có nhiều bộ nhớ ảo đang được sử dụng hơn là bộ nhớ vật lý để lưu trữ nó.
Donal Fellows

5

Nếu chương trình đã từng được biến thành một thành phần động ("plugin") được tải vào không gian địa chỉ của chương trình khác, nó sẽ rất rắc rối, ngay cả trên một hệ điều hành có quản lý bộ nhớ gọn gàng. Chúng tôi thậm chí không phải nghĩ về việc mã được chuyển đến các hệ thống kém khả năng hơn.

Mặt khác, việc giải phóng tất cả bộ nhớ có thể ảnh hưởng đến hiệu suất dọn dẹp của chương trình.

Một chương trình tôi đang làm việc, một trường hợp thử nghiệm nhất định cần 30 giây hoặc hơn để chương trình thoát ra, vì nó đang đệ quy qua đồ thị của tất cả bộ nhớ động và giải phóng từng phần một.

Một giải pháp hợp lý là có khả năng ở đó và bao phủ nó bằng các trường hợp thử nghiệm, nhưng hãy tắt nó trong mã sản xuất để ứng dụng thoát nhanh.


5

Tất cả các hệ điều hành xứng đáng với danh hiệu này sẽ dọn dẹp mớ hỗn độn mà quy trình của bạn thực hiện sau khi chấm dứt. Nhưng luôn có những sự kiện không lường trước được, điều gì sẽ xảy ra nếu nó bị từ chối quyền truy cập bằng cách nào đó và một lập trình viên kém nào đó không lường trước được khả năng đó và vì vậy nó không thử lại sau một chút nữa thì sao? Luôn an toàn hơn khi chỉ tự dọn dẹp NẾU rò rỉ bộ nhớ là nhiệm vụ quan trọng - nếu không thì không thực sự xứng đáng với nỗ lực IMO nếu nỗ lực đó tốn kém.

Chỉnh sửa: Bạn cần phải làm sạch các rò rỉ bộ nhớ nếu chúng ở đúng vị trí mà chúng sẽ tích tụ, như trong các vòng lặp. Rò rỉ bộ nhớ mà tôi nói đến là những lỗi tích tụ trong thời gian liên tục trong suốt quá trình của chương trình, nếu bạn bị rò rỉ bất kỳ loại nào khác thì rất có thể sớm muộn gì cũng là một vấn đề nghiêm trọng.

Về mặt kỹ thuật, nếu rò rỉ của bạn thuộc loại bộ nhớ 'phức tạp' O (1) thì chúng vẫn ổn trong hầu hết các trường hợp, O (logn) đã khó chịu (và trong một số trường hợp gây tử vong) và O (N) + không thể dung nạp được.


3

Bộ nhớ dùng chung trên các hệ thống tuân thủ POSIX vẫn tồn tại cho đến khi shm_unlink được gọi hoặc hệ thống được khởi động lại.


2

Nếu bạn có giao tiếp giữa các quy trình, điều này có thể dẫn đến các quy trình khác không bao giờ hoàn thành và tiêu tốn tài nguyên tùy thuộc vào giao thức.

Để đưa ra một ví dụ, tôi đã từng thử nghiệm in bằng máy in PDF bằng Java khi tôi kết thúc JVM ở giữa công việc của máy in, quá trình tạo tệp PDF vẫn hoạt động và tôi phải tắt nó trong trình quản lý tác vụ trước khi có thể thử in lại.

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.