Sự phức tạp của lập trình không được quản lý bộ nhớ là gì?


24

Hay nói cách khác, những vấn đề cụ thể nào đã thu gom rác tự động giải quyết? Tôi chưa bao giờ thực hiện lập trình cấp thấp, vì vậy tôi không biết giải phóng tài nguyên có thể phức tạp đến mức nào.

Các loại lỗi mà GC giải quyết dường như (ít nhất là đối với người quan sát bên ngoài) là loại điều mà một lập trình viên biết rõ ngôn ngữ, thư viện, khái niệm, thành ngữ, v.v., sẽ không làm được. Nhưng tôi có thể sai: việc xử lý bộ nhớ thủ công có thực sự phức tạp không?


3
Vui lòng mở rộng để cho chúng tôi biết câu hỏi của bạn không được trả lời như thế nào bởi bài viết trên Wikipedia về bộ sưu tập garbace và cụ thể hơn là phần về lợi ích của nó
yannis

Một lợi ích khác là bảo mật, ví dụ, lỗi tràn bộ đệm có khả năng khai thác cao và nhiều lỗ hổng bảo mật khác phát sinh từ quản lý bộ nhớ (mis).
Người sử dụng Stuper

7
@StuperUser: Điều đó không liên quan gì đến nguồn gốc của bộ nhớ. Bạn có thể đệm bộ nhớ tràn ra từ một GC tốt. Thực tế là các ngôn ngữ GC thường ngăn chặn điều này là trực giao và các ngôn ngữ chậm hơn ba mươi năm so với công nghệ GC mà bạn đang so sánh chúng để cung cấp bảo vệ chống tràn bộ đệm.
DeadMG

Câu trả lời:


29

Tôi chưa bao giờ thực hiện lập trình cấp thấp, vì vậy tôi không biết giải phóng tài nguyên có thể phức tạp đến mức nào.

Thật buồn cười khi định nghĩa về "cấp thấp" thay đổi theo thời gian. Khi tôi lần đầu tiên học lập trình, bất kỳ ngôn ngữ nào cung cấp mô hình heap được tiêu chuẩn hóa để tạo ra một mô hình phân bổ / miễn phí đơn giản đều có thể được coi là cấp cao thực sự. Trong lập trình cấp thấp , bạn phải tự theo dõi bộ nhớ, (không phải phân bổ, mà là chính các vị trí bộ nhớ!) Hoặc viết phân bổ heap của riêng bạn nếu bạn cảm thấy thực sự ưa thích.

Phải nói rằng, thực sự không có gì đáng sợ hay "phức tạp" về điều đó cả. Hãy nhớ khi bạn còn là một đứa trẻ và mẹ bạn bảo bạn cất đồ chơi của bạn khi bạn chơi xong với chúng, rằng nó không phải là người giúp việc của bạn và sẽ không dọn phòng cho bạn? Quản lý bộ nhớ chỉ đơn giản là nguyên tắc tương tự được áp dụng cho mã. (GC giống như có một người giúp việc sẽ dọn dẹp sau bạn, nhưng cô ấy rất lười biếng và hơi không biết gì.) Nguyên tắc của nó rất đơn giản: Mỗi biến trong mã của bạn có một và chỉ một chủ sở hữu và đó là trách nhiệm của chủ sở hữu đó giải phóng bộ nhớ của biến khi không còn cần thiết. ( Nguyên tắc sở hữu duy nhất) Điều này yêu cầu một cuộc gọi cho mỗi lần phân bổ và một số chương trình tồn tại tự động hóa quyền sở hữu và dọn dẹp bằng cách này hay cách khác để bạn thậm chí không phải viết cuộc gọi đó vào mã của riêng mình.

Thu gom rác được cho là để giải quyết hai vấn đề. Nó luôn luôn làm một công việc rất tệ ở một trong số họ, và tùy thuộc vào việc thực hiện có thể hoặc không thể làm tốt với người khác. Vấn đề là rò rỉ bộ nhớ (giữ bộ nhớ sau khi bạn hoàn thành nó) và treo các tài liệu tham khảo (giải phóng bộ nhớ trước khi bạn hoàn thành với nó.) Hãy xem xét cả hai vấn đề:

Tài liệu tham khảo lơ lửng: Thảo luận về vấn đề này trước tiên vì nó thực sự nghiêm túc. Bạn đã có hai con trỏ đến cùng một đối tượng. Bạn giải phóng một trong số họ và không để ý đến người khác. Sau đó, tại một số điểm sau đó, bạn cố gắng đọc (hoặc viết hoặc miễn phí) cái thứ hai. Hành vi không xác định xảy ra sau đó. Nếu bạn không chú ý đến nó, bạn có thể dễ dàng làm hỏng bộ nhớ của mình. Bộ sưu tập rác được cho là không thể giải quyết vấn đề này bằng cách đảm bảo rằng không có gì được giải phóng cho đến khi tất cả các tham chiếu đến nó không còn nữa. Trong một ngôn ngữ được quản lý hoàn toàn, điều này gần như hoạt động, cho đến khi bạn phải xử lý các tài nguyên bộ nhớ bên ngoài, không được quản lý. Sau đó, nó quay lại quảng trường 1. Và trong một ngôn ngữ không được quản lý, mọi thứ vẫn phức tạp hơn. (Chọc quanh Mozilla '

May mắn thay, xử lý vấn đề này về cơ bản là một vấn đề được giải quyết. Bạn không cần một trình thu gom rác, bạn cần một trình quản lý bộ nhớ gỡ lỗi. Tôi sử dụng Delphi, ví dụ, và với một thư viện bên ngoài duy nhất và một lệnh biên dịch đơn giản, tôi có thể đặt bộ cấp phát thành "Chế độ gỡ lỗi đầy đủ". Điều này bổ sung thêm chi phí hiệu năng không đáng kể (dưới 5%) để cho phép một số tính năng theo dõi bộ nhớ đã sử dụng. Nếu tôi giải phóng một đối tượng, nó sẽ lấp đầy bộ nhớ của nó với0x80byte (dễ nhận biết trong trình gỡ lỗi) và nếu tôi từng cố gắng gọi một phương thức ảo (bao gồm cả hàm hủy) trên một đối tượng được giải phóng, nó sẽ thông báo và làm gián đoạn chương trình với một hộp lỗi với ba dấu vết ngăn xếp - khi đối tượng được tạo, khi nó được giải phóng, và tôi đang ở đâu - cộng với một số thông tin hữu ích khác, sau đó đưa ra một ngoại lệ. Điều này rõ ràng là không phù hợp cho các bản dựng phát hành, nhưng nó làm cho việc theo dõi và sửa chữa các vấn đề tham chiếu lơ lửng trở nên tầm thường.

Vấn đề thứ hai là rò rỉ bộ nhớ. Đây là những gì xảy ra khi bạn tiếp tục giữ bộ nhớ được phân bổ khi bạn không còn cần nó nữa. Nó có thể xảy ra trong bất kỳ ngôn ngữ nào, có hoặc không có bộ sưu tập rác và chỉ có thể được sửa bằng cách viết mã của bạn ngay. Bộ sưu tập rác giúp giảm thiểu một dạng rò rỉ bộ nhớ cụ thể , loại xảy ra khi bạn không có tài liệu tham khảo hợp lệ cho một phần bộ nhớ chưa được giải phóng, có nghĩa là bộ nhớ vẫn được phân bổ cho đến khi chương trình kết thúc. Thật không may, cách duy nhất để thực hiện điều này một cách tự động là bằng cách biến mọi phân bổ thành rò rỉ bộ nhớ!

Có lẽ tôi sẽ bị những người đề xướng GC làm phiền nếu tôi cố gắng nói điều gì đó như vậy, vì vậy hãy cho phép tôi giải thích. Hãy nhớ rằng định nghĩa về rò rỉ bộ nhớ đang giữ cho bộ nhớ được phân bổ khi bạn không còn cần nó nữa. Ngoài việc không có tài liệu tham khảo đến một cái gì đó, bạn cũng có thể rò rỉ bộ nhớ bằng cách có một tham chiếu không cần thiết đến nó, chẳng hạn như giữ nó trong một đối tượng chứa khi bạn nên giải phóng nó. Tôi đã thấy một số rò rỉ bộ nhớ gây ra bởi việc này và họ rất khó theo dõi xem bạn có bị chứng nhận hay không, vì chúng liên quan đến một tham chiếu hoàn toàn hợp lệ đến bộ nhớ và không có "lỗi" rõ ràng nào cho các công cụ gỡ lỗi bắt lấy. Theo tôi biết, không có công cụ tự động nào cho phép bạn bắt được loại rò rỉ bộ nhớ này.

Vì vậy, một người thu gom rác chỉ quan tâm đến sự rò rỉ bộ nhớ không có tài liệu tham khảo, bởi vì đó là loại duy nhất có thể được xử lý theo kiểu tự động. Nếu nó có thể xem tất cả các tài liệu tham khảo của bạn cho mọi thứ và giải phóng mọi đối tượng ngay khi nó không có tài liệu tham khảo nào chỉ vào nó, thì nó sẽ hoàn hảo, ít nhất là liên quan đến vấn đề không có tài liệu tham khảo. Làm điều này theo cách tự động được gọi là đếm tham chiếu, và nó có thể được thực hiện trong một số tình huống hạn chế, nhưng nó có vấn đề riêng để giải quyết. (Ví dụ: đối tượng A giữ tham chiếu đến đối tượng B, chứa tham chiếu đến đối tượng A. Trong sơ đồ đếm tham chiếu, không đối tượng nào có thể được giải phóng tự động, ngay cả khi không có tham chiếu bên ngoài đến A hoặc B.) người thu gom rác sử dụng truy tìmthay vào đó: Bắt đầu với một tập hợp các đối tượng đã biết, tìm tất cả các đối tượng mà chúng tham chiếu, tìm tất cả các đối tượng mà chúng tham chiếu, và cứ thế đệ quy cho đến khi bạn tìm thấy mọi thứ. Bất cứ thứ gì không được tìm thấy trong quá trình truy tìm đều là rác và có thể vứt đi. (Tất nhiên, việc này thành công đòi hỏi một ngôn ngữ được quản lý đặt ra một số hạn chế nhất định đối với hệ thống loại để đảm bảo rằng trình thu gom rác theo dõi luôn có thể cho biết sự khác biệt giữa một tham chiếu và một phần bộ nhớ ngẫu nhiên trông giống như một con trỏ.)

Có hai vấn đề với truy tìm. Đầu tiên, nó chậm và trong khi nó đang diễn ra, chương trình phải tạm dừng ít nhiều để tránh điều kiện cuộc đua. Điều này có thể dẫn đến các trục trặc thực thi đáng chú ý khi chương trình được cho là tương tác với người dùng hoặc làm chậm hiệu suất trong ứng dụng máy chủ. Điều này có thể được giảm thiểu bằng các kỹ thuật khác nhau, chẳng hạn như chia bộ nhớ được phân bổ thành "thế hệ" theo nguyên tắc nếu phân bổ không được thu thập lần đầu tiên bạn thử, có thể sẽ tồn tại trong một thời gian. Cả .NET framework và JVM đều sử dụng các trình thu gom rác thế hệ.

Thật không may, điều này dẫn đến vấn đề thứ hai: bộ nhớ không được giải phóng khi bạn hoàn thành nó. Trừ khi dấu vết chạy ngay lập tức sau khi bạn kết thúc với một đối tượng, nó sẽ bám xung quanh cho đến dấu vết tiếp theo, hoặc thậm chí lâu hơn nếu nó vượt qua thế hệ đầu tiên. Trong thực tế, một trong những giải thích tốt nhất về trình thu gom rác .NET mà tôi đã thấy giải thích rằng, để làm cho quá trình này nhanh nhất có thể, GC phải trì hoãn việc thu thập càng lâu càng tốt! Vì vậy, vấn đề rò rỉ bộ nhớ được "giải quyết" khá kỳ lạ bằng cách rò rỉ càng nhiều bộ nhớ càng lâu càng tốt! Ý tôi là khi tôi nói rằng một GC biến mọi phân bổ thành rò rỉ bộ nhớ. Trong thực tế, không có đảm bảo rằng bất kỳ đối tượng nhất định sẽ không bao giờ được thu thập.

Tại sao điều này là một vấn đề, khi bộ nhớ vẫn được thu hồi khi cần thiết? Đối với một vài lý do. Đầu tiên, hãy tưởng tượng việc phân bổ một đối tượng lớn (ví dụ như một bitmap), chiếm một lượng bộ nhớ đáng kể. Và ngay sau khi bạn hoàn thành nó, bạn cần một đối tượng lớn khác có cùng dung lượng (hoặc gần bằng). Nếu đối tượng đầu tiên được giải phóng, đối tượng thứ hai có thể sử dụng lại bộ nhớ của nó. Nhưng trên một hệ thống thu gom rác, bạn vẫn có thể chờ đợi dấu vết tiếp theo chạy, và vì vậy bạn sẽ lãng phí bộ nhớ một cách không cần thiết cho một đối tượng lớn thứ hai. Về cơ bản, đó là một điều kiện cuộc đua.

Thứ hai, việc giữ bộ nhớ không cần thiết, đặc biệt là với số lượng lớn, có thể gây ra sự cố trong hệ thống đa nhiệm hiện đại. Nếu bạn chiếm quá nhiều bộ nhớ vật lý, nó có thể khiến chương trình của bạn hoặc các chương trình khác phải chuyển trang (hoán đổi một số bộ nhớ của chúng ra đĩa), điều này thực sự làm mọi thứ chậm lại. Đối với một số sytem nhất định, chẳng hạn như máy chủ, phân trang không chỉ có thể làm chậm hệ thống, nó có thể làm hỏng toàn bộ nếu nó đang tải.

Giống như vấn đề tham chiếu lơ lửng, vấn đề không tham chiếu có thể được giải quyết bằng trình quản lý bộ nhớ gỡ lỗi. Một lần nữa, tôi sẽ đề cập đến Chế độ gỡ lỗi hoàn toàn từ trình quản lý bộ nhớ FastMM của Delphi, vì đây là chế độ tôi quen thuộc nhất. (Tôi chắc chắn các hệ thống tương tự tồn tại cho các ngôn ngữ khác.)

Khi một chương trình chạy dưới FastMM kết thúc, bạn có thể tùy ý báo cáo sự tồn tại của tất cả các phân bổ không bao giờ được giải phóng. Chế độ gỡ lỗi hoàn toàn sẽ tiến thêm một bước: nó có thể lưu tệp vào đĩa không chỉ chứa loại phân bổ, mà còn theo dõi ngăn xếp từ khi được phân bổ và thông tin gỡ lỗi khác, cho mỗi phân bổ bị rò rỉ. Điều này làm cho việc theo dõi xuống bộ nhớ không tham chiếu rò rỉ tầm thường.

Khi bạn thực sự nhìn vào nó, bộ sưu tập rác có thể hoặc không thể làm tốt với việc ngăn chặn các tài liệu tham khảo lơ lửng, và phổ biến là làm một công việc tồi tệ trong việc xử lý rò rỉ bộ nhớ. Trên thực tế, một ưu điểm của nó không phải là bộ sưu tập rác, mà là một tác dụng phụ: nó cung cấp một cách tự động để thực hiện nén heap. Điều này có thể ngăn chặn một vấn đề phức tạp (cạn kiệt bộ nhớ thông qua phân mảnh heap) có thể giết chết các chương trình chạy liên tục trong một thời gian dài và có mức độ bộ nhớ cao, và việc nén heap là không thể nếu không có bộ sưu tập rác. Tuy nhiên, bất kỳ bộ cấp phát bộ nhớ tốt nào hiện nay đều sử dụng xô để giảm thiểu phân mảnh, điều đó có nghĩa là phân mảnh chỉ thực sự trở thành vấn đề trong hoàn cảnh khắc nghiệt. Đối với một chương trình trong đó phân mảnh heap có thể là một vấn đề, nó ' Nên sử dụng máy thu gom rác. Nhưng IMO trong bất kỳ trường hợp nào khác, việc sử dụng bộ sưu tập rác là tối ưu hóa sớm và các giải pháp tốt hơn tồn tại cho các vấn đề mà nó "giải quyết".


5
Tôi thích câu trả lời này - tôi tiếp tục đọc nó mọi lúc mọi nơi. Không thể đưa ra một nhận xét có liên quan để tất cả những gì tôi có thể nói là - cảm ơn bạn.
vemv

3
Tôi muốn chỉ ra rằng có, các GC có xu hướng "rò rỉ" bộ nhớ (ít nhất là trong một thời gian), nhưng đây không phải là vấn đề vì nó sẽ thu thập bộ nhớ khi bộ cấp phát bộ nhớ không thể phân bổ bộ nhớ trước khi thu thập. Với ngôn ngữ không phải là GC, rò rỉ luôn bị rò rỉ, có nghĩa là bạn thực sự có thể hết bộ nhớ do có quá nhiều bộ nhớ không được kiểm soát. "thu gom rác là tối ưu hóa sớm" ... GC không phải là tối ưu hóa và không được thiết kế theo ý nghĩ đó. Nếu không, câu trả lời tốt.
Thomas Eding

7
@ThomasEding: GC chắc chắn là một tối ưu hóa; nó tối ưu hóa cho nỗ lực lập trình tối thiểu, với chi phí hiệu năng và các số liệu chất lượng chương trình khác nhau.
Mason Wheeler

5
Thật buồn cười khi bạn chỉ đến trình theo dõi lỗi của Mozilla tại một thời điểm, bởi vì Mozilla đã đi đến một kết luận hoàn toàn khác. Firefox đã và tiếp tục có vô số vấn đề bảo mật đến từ các lỗi quản lý bộ nhớ. Lưu ý rằng đây không phải là cách dễ dàng sửa lỗi một khi được phát hiện --- thông thường, thiệt hại đã được thực hiện vào thời điểm các nhà phát triển nhận thức được vấn đề. Mozilla đang tài trợ chính xác ngôn ngữ lập trình Rust để giúp ngăn chặn những lỗi như vậy được đưa ra ngay từ đầu.

1
Rust không sử dụng bộ sưu tập rác, mặc dù vậy, nó sử dụng tính năng tham chiếu chính xác cách Mason mô tả, chỉ với kiểm tra thời gian biên dịch mở rộng thay vì phải sử dụng trình gỡ lỗi để phát hiện lỗi trong thời gian chạy ...
Sean Burton

13

Xem xét một kỹ thuật quản lý bộ nhớ không được thu gom rác từ thời đại tương đương như các công cụ thu gom rác được sử dụng trong các hệ thống phổ biến hiện nay, chẳng hạn như RAII của C ++. Với cách tiếp cận này, thì chi phí không sử dụng bộ sưu tập rác tự động là tối thiểu và GC đưa ra rất nhiều vấn đề của riêng nó. Như vậy, tôi đề nghị rằng "Không nhiều" là câu trả lời cho vấn đề của bạn.

Hãy nhớ rằng, khi mọi người nghĩ về phi-GC, họ nghĩ mallocfree. Nhưng đây là một sai lầm logic khổng lồ - bạn sẽ so sánh việc quản lý tài nguyên không phải là đầu thập niên 1970 với những người thu gom rác vào cuối những năm 90. Đây rõ ràng là một so sánh khá không công bằng - những người thu gom rác được sử dụng khi mallocfreeđược thiết kế quá chậm để chạy bất kỳ chương trình có ý nghĩa nào, nếu tôi nhớ chính xác. So sánh một cái gì đó từ một khoảng thời gian tương đương mơ hồ, ví dụ unique_ptr, có ý nghĩa hơn nhiều.

Người thu gom rác có thể xử lý các chu trình tham chiếu dễ dàng hơn, mặc dù đây là những kinh nghiệm khá hiếm. Ngoài ra, các GC chỉ có thể "ném lên" mã bởi vì GC sẽ đảm nhiệm tất cả việc quản lý bộ nhớ, nghĩa là chúng có thể dẫn đến chu kỳ phát triển nhanh hơn.

Mặt khác, họ có xu hướng gặp phải những vấn đề lớn khi xử lý bộ nhớ đến từ bất cứ đâu ngoại trừ nhóm GC của chính họ. Ngoài ra, họ mất rất nhiều lợi ích khi tham gia đồng thời, vì dù sao bạn cũng phải xem xét quyền sở hữu đối tượng.

Chỉnh sửa: Nhiều điều bạn đề cập không liên quan gì đến GC. Bạn đang nhầm lẫn quản lý bộ nhớ và định hướng đối tượng. Xem, đây là điều: Nếu bạn lập trình trong một hệ thống hoàn chỉnh không được quản lý, như C ++, bạn có thể kiểm tra giới hạn bao nhiêu tùy thích và các lớp Container tiêu chuẩn sẽ cung cấp nó. Chẳng có gì về việc kiểm tra giới hạn, ví dụ, hoặc gõ mạnh.

Các vấn đề bạn đề cập được giải quyết bằng hướng đối tượng, không phải là GC. Nguồn gốc của bộ nhớ mảng và đảm bảo bạn không viết bên ngoài nó là các khái niệm trực giao.

Chỉnh sửa: Điều đáng chú ý là các kỹ thuật nâng cao hơn có thể tránh được sự cần thiết của bất kỳ hình thức phân bổ bộ nhớ động nào. Ví dụ, hãy xem xét việc sử dụng cái này , thực hiện kết hợp Y trong C ++ mà không có phân bổ động nào cả.


Các cuộc thảo luận mở rộng ở đây đã được làm sạch: nếu mọi người có thể mang nó đi trò chuyện để thảo luận thêm về chủ đề này, tôi thực sự đánh giá cao nó.

@DeadMG, bạn có biết người phối hợp phải làm gì không? Nó được cho là COMBINE. Theo định nghĩa, combinator là một hàm không có bất kỳ biến miễn phí nào.
SK-logic

2
@ SK-logic: Tôi có thể đã chọn thực hiện nó hoàn toàn bằng mẫu và không có bất kỳ biến thành viên nào. Nhưng sau đó, bạn sẽ không thể vượt qua trong các lần đóng cửa, điều này hạn chế đáng kể tính hữu dụng của nó. Muốn đến để trò chuyện?
DeadMG

@DeadMG, một định nghĩa rất rõ ràng. Không có biến miễn phí. Tôi xem xét bất kỳ ngôn ngữ nào "đủ chức năng" nếu có thể định nghĩa Y-combinator (đúng cách, không phải theo cách của bạn). Một dấu "+" lớn là nếu có thể xác định nó thông qua các tổ hợp S, K và I. Nếu không thì ngôn ngữ không đủ biểu cảm.
SK-logic

4
@ SK-logic: Tại sao bạn không đến trò chuyện , như người điều hành tử tế đã hỏi? Ngoài ra, một tổ hợp Y là một tổ hợp Y, nó thực hiện công việc hoặc không. Phiên bản Haskell của Y-combinator về cơ bản giống hệt như phiên bản này, chỉ là trạng thái thể hiện được ẩn khỏi bạn.
DeadMG

11

"Tự do khỏi phải lo lắng về việc giải phóng tài nguyên" mà các ngôn ngữ thu gom rác được cho là cung cấp ở một mức độ đáng kể là một ảo ảnh. Tiếp tục thêm nội dung vào bản đồ mà không bao giờ xóa bất kỳ nội dung nào và bạn sẽ sớm hiểu những gì tôi đang nói.

Trên thực tế, rò rỉ bộ nhớ là khá thường xuyên trong các chương trình được viết bằng ngôn ngữ GCed, bởi vì các ngôn ngữ này có xu hướng làm cho các lập trình viên lười biếng, và khiến họ có được cảm giác an toàn sai lầm rằng ngôn ngữ sẽ luôn luôn (bằng cách nào đó) quan tâm đến mọi đối tượng mà họ không muốn phải suy nghĩ nữa.

Bộ sưu tập rác chỉ đơn giản là một phương tiện cần thiết cho các ngôn ngữ có mục tiêu cao cả hơn: coi mọi thứ như một con trỏ đến một đối tượng, đồng thời ẩn với lập trình viên rằng đó là một con trỏ, để lập trình viên không thể cam kết tự tử bằng cách thử số học con trỏ và tương tự. Tất cả mọi thứ là một đối tượng có nghĩa là các ngôn ngữ được phân bổ cần phải phân bổ các đối tượng thường xuyên hơn các ngôn ngữ không được phân loại, điều đó có nghĩa là nếu chúng đặt gánh nặng xử lý các đối tượng đó lên lập trình viên, chúng sẽ cực kỳ kém hấp dẫn.

Ngoài ra, bộ sưu tập rác rất hữu ích để cung cấp cho lập trình viên khả năng viết mã chặt chẽ, thao tác các đối tượng bên trong các biểu thức, theo kiểu lập trình chức năng, mà không phải chia các biểu thức thành các câu lệnh riêng biệt để cung cấp cho việc sắp xếp mọi thứ đối tượng duy nhất tham gia vào biểu thức.

Ngoài tất cả những điều đó, xin lưu ý rằng khi bắt đầu câu trả lời của tôi, tôi đã viết "đó là một ảo ảnh ở một mức độ đáng kể ". Tôi không viết rằng đó chỉ là ảo ảnh. Tôi thậm chí không viết rằng nó chủ yếu chỉ là ảo ảnh. Bộ sưu tập rác rất hữu ích trong việc lấy đi từ lập trình viên nhiệm vụ trọng tâm là tham gia vào việc giải quyết các đối tượng của mình. Vì vậy, trong ý nghĩa này, nó là một tính năng năng suất.


4

Công cụ thu gom rác không giải quyết bất kỳ "lỗi" nào. Nó là một phần không cần thiết của một số ngữ nghĩa ngôn ngữ cấp cao. Với một GC có thể xác định các mức trừu tượng cao hơn, chẳng hạn như đóng từ vựng và tương tự, trong khi với quản lý bộ nhớ thủ công, các trừu tượng đó sẽ bị rò rỉ, ràng buộc không cần thiết với các cấp quản lý tài nguyên thấp hơn.

Một "nguyên tắc sở hữu duy nhất", được đề cập trong các ý kiến, là một ví dụ điển hình cho sự trừu tượng bị rò rỉ như vậy. Nhà phát triển hoàn toàn không nên quan tâm đến số lượng liên kết đến bất kỳ trường hợp cấu trúc dữ liệu cơ bản cụ thể nào, nếu không, bất kỳ đoạn mã nào sẽ không chung chung và minh bạch nếu không có một số lượng lớn các yêu cầu và giới hạn bổ sung (không thể nhìn thấy trực tiếp trong chính mã) . Một mã như vậy không thể được tạo thành mã cấp cao hơn, đó là sự vi phạm không thể chấp nhận được của các lớp nguyên tắc phân tách trách nhiệm (một khối xây dựng chính của công nghệ phần mềm, rất tiếc, hầu hết các nhà phát triển cấp thấp đều không tôn trọng).


1
@Mason Wheeler, thậm chí C ++ thực hiện một hình thức đóng cửa rất hạn chế. Nhưng gần như không phải là một đóng cửa thích hợp, thường có thể sử dụng.
SK-logic

1
Bạn sai rồi. Không có GC nào có thể bảo vệ bạn khỏi thực tế là bạn không thể tham chiếu đến các biến stack. Và thật buồn cười - trong C ++, bạn cũng có thể sử dụng phương pháp "Sao chép một con trỏ vào một biến được phân bổ động, cũng sẽ được tự động phá hủy".
DeadMG

1
@DeadMG, bạn không thấy rằng mã của bạn đang rò rỉ các thực thể cấp thấp thông qua bất kỳ cấp độ nào khác mà bạn xây dựng trên đầu trang?
SK-logic

1
@ SK-Logic: OK, chúng tôi có một vấn đề thuật ngữ. Định nghĩa của bạn về "đóng cửa thực sự" là gì và họ có thể làm gì mà việc đóng cửa của Delphi không thể? (Và bao gồm mọi thứ về quản lý bộ nhớ trong định nghĩa của bạn đang di chuyển các mục tiêu mục tiêu. Hãy nói về hành vi, không phải chi tiết thực hiện.)
Mason Wheeler

1
@ SK-Logic: ... và bạn có một ví dụ về điều gì đó có thể được thực hiện với các đóng cửa đơn giản-chưa được xử lý-lambda mà việc đóng của Delphi không thể thực hiện được không?
Mason Wheeler

2

Thực sự, quản lý bộ nhớ của riêng bạn chỉ là một nguồn lỗi tiềm năng.

Nếu bạn quên một cuộc gọi đến free(hoặc bất cứ điều gì tương đương với bất kỳ ngôn ngữ nào bạn đang sử dụng), chương trình của bạn có thể vượt qua tất cả các bài kiểm tra của nó, nhưng bộ nhớ bị rò rỉ. Và trong một chương trình phức tạp vừa phải, thật dễ dàng bỏ qua một cuộc gọi đến free.


3
Bỏ lỡ freekhông phải là điều tồi tệ nhất. Sớm freehơn nhiều tàn phá.
Herby

2
Và gấp đôi free!
quant_dev

Hehe! Tôi sẽ đi cùng với cả hai ý kiến ​​trên. Bản thân tôi chưa bao giờ phạm phải một trong những sự vi phạm này (theo như tôi biết), nhưng tôi có thể thấy những tác động có thể khủng khiếp như thế nào. Câu trả lời từ quant_dev nói lên tất cả - những sai lầm trong phân bổ bộ nhớ và phân bổ lại rất khó tìm và sửa chữa.
Dawood nói phục hồi Monica

1
Đây là một ngụy biện. Bạn đang so sánh "đầu năm 1970" với "cuối năm 1990". Các GC tồn tại vào thời điểm đó mallocfreelà con đường không phải là GC để đi là quá chậm để có thể hữu ích cho bất cứ điều gì. Bạn cần so sánh nó với một cách tiếp cận phi GC hiện đại, như RAII.
DeadMG

2
@DeadMG RAII không phải là quản lý bộ nhớ thủ công
quant_dev

2

Tài nguyên thủ công không chỉ tẻ nhạt, mà còn khó gỡ lỗi. Nói cách khác, không chỉ tẻ nhạt để làm cho đúng, mà cả khi bạn sai, không rõ vấn đề ở đâu. Điều này là do, không giống như chia cho số 0, các tác động của lỗi hiển thị từ nguồn lỗi và việc kết nối các dấu chấm đòi hỏi thời gian, sự chú ý và kinh nghiệm.


1

Tôi nghĩ rằng bộ sưu tập rác nhận được rất nhiều tín dụng cho những cải tiến ngôn ngữ không liên quan gì đến GC, ngoài việc là một phần của một làn sóng tiến bộ lớn.

Một lợi ích vững chắc cho GC mà tôi biết là bạn có thể đặt một đối tượng miễn phí trong chương trình của mình và biết rằng nó sẽ biến mất khi mọi người hoàn thành nó. Bạn có thể chuyển nó sang phương thức của lớp khác và không phải lo lắng về nó. Bạn không quan tâm những phương thức khác mà nó được truyền vào, hoặc những lớp khác tham chiếu nó. (Rò rỉ bộ nhớ là trách nhiệm của lớp tham chiếu một đối tượng, không phải lớp đã tạo ra nó.)

Nếu không có GC, bạn phải theo dõi toàn bộ vòng đời của bộ nhớ được phân bổ. Mỗi khi bạn chuyển một địa chỉ lên hoặc xuống từ chương trình con đã tạo ra nó, bạn có một tham chiếu ngoài tầm kiểm soát tới bộ nhớ đó. Trong những ngày xưa tồi tệ, thậm chí chỉ có một luồng, đệ quy và hệ điều hành ornery (Windows NT) khiến tôi không thể kiểm soát quyền truy cập vào bộ nhớ được phân bổ. Tôi đã phải sử dụng phương thức miễn phí trong hệ thống phân bổ của riêng mình để giữ các khối bộ nhớ trong một thời gian cho đến khi tất cả các tham chiếu bị xóa. Thời gian nắm giữ là phỏng đoán thuần túy, nhưng nó đã làm việc.

Vì vậy, đó là lợi ích duy nhất mà tôi biết, nhưng tôi không thể sống thiếu nó. Tôi không nghĩ rằng bất kỳ loại OOP nào sẽ bay mà không có nó.


1
Ngay từ đầu tôi, Delphi và C ++ đều khá thành công với tư cách là ngôn ngữ OOP mà không cần bất kỳ ngôn ngữ nào. Tất cả những gì bạn cần để ngăn chặn "các tham chiếu ngoài tầm kiểm soát" là một chút kỷ luật. Nếu bạn hiểu Nguyên tắc sở hữu duy nhất, (xem câu trả lời của tôi), các vấn đề bạn đang nói ở đây sẽ trở thành toàn bộ vấn đề.
Mason Wheeler

@MasonWheeler: Khi đến lúc giải phóng đối tượng chủ sở hữu, nó cần biết tất cả các địa điểm mà các đối tượng sở hữu của nó được tham chiếu. Duy trì thông tin này và sử dụng nó để loại bỏ các tài liệu tham khảo có vẻ như rất nhiều công việc đối với tôi. Tôi thường tìm thấy các tài liệu tham khảo không thể được xóa khá. Tôi đã phải đánh dấu chủ sở hữu là đã xóa, sau đó đưa nó vào cuộc sống định kỳ để xem liệu nó có thể tự giải thoát một cách an toàn không. Tôi chưa bao giờ sử dụng Delphi, nhưng với một sự hy sinh nhỏ trong hiệu quả thực thi, C # / Java đã mang lại cho tôi một sự thúc đẩy lớn trong thời gian phát triển so với C ++. (Không phải tất cả là do GC, nhưng nó đã giúp.)
RalphChapin

1

Rò rỉ vật lý

Các loại lỗi mà GC giải quyết dường như (ít nhất là đối với người quan sát bên ngoài) là loại điều mà một lập trình viên biết rõ ngôn ngữ, thư viện, khái niệm, thành ngữ, v.v., sẽ không làm được. Nhưng tôi có thể sai: việc xử lý bộ nhớ thủ công có thực sự phức tạp không?

Đến từ đầu C làm cho việc quản lý bộ nhớ trở nên thủ công và phát âm càng tốt để chúng ta so sánh các thái cực (C ++ chủ yếu tự động hóa quản lý bộ nhớ mà không cần GC), tôi nói "không thực sự" theo nghĩa so sánh với GC khi nó đến rò rỉ . Một người mới bắt đầu và đôi khi thậm chí là một pro có thể quên viết freecho một cái nhất định malloc. Nó chắc chắn xảy ra.

Tuy nhiên, có những công cụ như valgrindphát hiện rò rỉ sẽ ngay lập tức phát hiện ra, khi thực thi mã, khi / khi những lỗi đó xảy ra xuống dòng mã chính xác. Khi được tích hợp vào CI, gần như không thể hợp nhất những lỗi đó và dễ dàng sửa chữa chúng. Vì vậy, nó không bao giờ là một vấn đề lớn trong bất kỳ nhóm / quy trình với các tiêu chuẩn hợp lý.

Cấp, có thể có một số trường hợp thực thi kỳ lạ bay theo radar thử nghiệm freekhông được gọi, có lẽ gặp phải lỗi đầu vào bên ngoài tối nghĩa như tệp bị hỏng trong trường hợp có thể hệ thống bị rò rỉ 32 byte hoặc thứ gì đó. Tôi nghĩ điều đó chắc chắn có thể xảy ra ngay cả dưới các tiêu chuẩn kiểm tra khá tốt và các công cụ phát hiện rò rỉ, nhưng cũng sẽ không quá quan trọng để rò rỉ một chút bộ nhớ vào một thứ gần như không bao giờ xảy ra. Chúng ta sẽ thấy một vấn đề lớn hơn nhiều khi chúng ta có thể rò rỉ tài nguyên lớn ngay cả trong các đường dẫn thực thi phổ biến bên dưới theo cách mà GC không thể ngăn chặn.

Thật khó khăn khi không có thứ gì đó giống như dạng giả của GC (đếm tham chiếu, ví dụ) khi thời gian tồn tại của một đối tượng cần được kéo dài cho một số hình thức xử lý hoãn / không đồng bộ, có lẽ bởi một luồng khác.

Con trỏ

Vấn đề thực sự với các hình thức quản lý bộ nhớ thủ công hơn không phải là rò rỉ đối với tôi. Có bao nhiêu ứng dụng gốc được viết bằng C hoặc C ++ mà chúng ta biết thực sự bị rò rỉ? Là nhân Linux bị rò rỉ? MySQL? Khóc 3? Máy trạm âm thanh kỹ thuật số và tổng hợp? Java VM có bị rò rỉ không (nó được triển khai bằng mã gốc)? Photoshop?

Nếu bất cứ điều gì, tôi nghĩ khi chúng ta nhìn xung quanh, các ứng dụng rò rỉ nhất có xu hướng là những ứng dụng được viết bằng cách sử dụng các sơ đồ GC. Nhưng trước khi thực hiện việc thu gom rác, mã gốc có một vấn đề quan trọng không liên quan đến rò rỉ bộ nhớ.

Vấn đề đối với tôi luôn là sự an toàn. Ngay cả khi chúng ta freeghi nhớ thông qua một con trỏ, nếu có bất kỳ con trỏ nào khác đến tài nguyên, chúng sẽ trở thành con trỏ lơ lửng (không hợp lệ).

Khi chúng tôi cố gắng truy cập vào các điểm của những con trỏ lơ lửng đó, cuối cùng chúng tôi sẽ chạy vào hành vi không xác định, mặc dù hầu như luôn luôn là một vi phạm segfault / truy cập dẫn đến một sự cố nghiêm trọng, ngay lập tức.

Tất cả những ứng dụng gốc mà tôi liệt kê ở trên có khả năng có một hoặc hai trường hợp khó hiểu có thể dẫn đến sự cố chủ yếu do vấn đề này và chắc chắn có một phần công bằng các ứng dụng kém chất lượng được viết bằng mã gốc rất nặng và thường gặp sự cố phần lớn do vấn đề này.

... Và đó là vì quản lý tài nguyên khó khăn cho dù bạn có sử dụng GC hay không. Sự khác biệt thực tế thường là rò rỉ (GC) hoặc sụp đổ (không có GC) khi đối mặt với một sai lầm dẫn đến quản lý tài nguyên sai.

Quản lý tài nguyên: Thu gom rác

Quản lý tài nguyên phức tạp là một quá trình thủ công, khó khăn, không có vấn đề gì. GC không thể tự động hóa bất cứ điều gì ở đây.

Hãy lấy một ví dụ về nơi chúng ta có đối tượng này, "Joe". Joe được tham chiếu bởi một số tổ chức mà anh ta là thành viên. Hàng tháng họ trích một khoản phí thành viên từ thẻ tín dụng của mình.

nhập mô tả hình ảnh ở đây

Chúng tôi cũng có một tài liệu tham khảo đến Joe để kiểm soát cuộc sống của anh ấy. Hãy nói rằng, là lập trình viên, chúng ta không còn cần Joe nữa. Anh ấy bắt đầu làm phiền chúng tôi và chúng tôi không còn cần những tổ chức này, anh ấy đã lãng phí thời gian để giao dịch với anh ấy. Vì vậy, chúng tôi cố gắng quét sạch anh ta khỏi bề mặt trái đất bằng cách xóa tham chiếu huyết mạch của anh ta.

nhập mô tả hình ảnh ở đây

... nhưng chờ đã, chúng tôi đang sử dụng bộ sưu tập rác. Mọi tham chiếu mạnh mẽ đến Joe sẽ giữ anh ta xung quanh. Vì vậy, chúng tôi cũng xóa các tham chiếu đến anh ta khỏi các tổ chức mà anh ta thuộc về (hủy đăng ký anh ta).

nhập mô tả hình ảnh ở đây

... Ngoại trừ rất tiếc, chúng tôi đã quên hủy đăng ký tạp chí của anh ấy! Bây giờ Joe vẫn còn trong ký ức, làm phiền chúng tôi và sử dụng hết tài nguyên, và công ty tạp chí cuối cùng cũng tiếp tục xử lý tư cách thành viên của Joe mỗi tháng.

Đây là lỗi chính có thể khiến nhiều chương trình phức tạp được viết bằng cách sử dụng các chương trình thu gom rác bị rò rỉ và bắt đầu sử dụng càng nhiều bộ nhớ trong thời gian dài hơn và có thể xử lý nhiều hơn (đăng ký tạp chí định kỳ). Họ đã quên xóa một hoặc nhiều tài liệu tham khảo đó, khiến người thu gom rác không thể thực hiện phép thuật của mình cho đến khi toàn bộ chương trình bị tắt.

Chương trình không sụp đổ, tuy nhiên. Nó hoàn toàn an toàn. Nó sẽ tiếp tục tăng cường trí nhớ và Joe sẽ vẫn nán lại xung quanh. Đối với nhiều ứng dụng, loại hành vi rò rỉ này mà chúng ta chỉ cần xử lý nhiều bộ nhớ / xử lý hơn có thể sẽ tốt hơn nhiều so với sự cố cứng, đặc biệt là có bao nhiêu bộ nhớ và khả năng xử lý của máy móc của chúng ta ngày nay.

Quản lý tài nguyên: Hướng dẫn sử dụng

Bây giờ, hãy xem xét lựa chọn thay thế nơi chúng tôi sử dụng con trỏ tới Joe và quản lý bộ nhớ thủ công, như vậy:

nhập mô tả hình ảnh ở đây

Những liên kết màu xanh này không quản lý cuộc sống của Joe. Nếu chúng ta muốn loại bỏ anh ta khỏi bề mặt trái đất, chúng tôi yêu cầu thủ công tiêu diệt anh ta, như vậy:

nhập mô tả hình ảnh ở đây

Bây giờ điều đó thường sẽ để lại cho chúng ta những con trỏ lơ lửng khắp nơi, vì vậy hãy xóa con trỏ cho Joe.

nhập mô tả hình ảnh ở đây

... Rất tiếc, chúng tôi lại mắc lỗi tương tự và quên hủy đăng ký tạp chí của Joe!

Ngoại trừ bây giờ chúng ta có một con trỏ lơ lửng. Khi thuê bao tạp chí cố gắng xử lý phí hàng tháng của Joe, toàn bộ thế giới sẽ bùng nổ - thông thường chúng ta gặp sự cố nghiêm trọng ngay lập tức.

Lỗi quản lý tài nguyên cơ bản tương tự này khi nhà phát triển quên xóa thủ công tất cả các con trỏ / tham chiếu đến tài nguyên có thể dẫn đến nhiều sự cố trong các ứng dụng gốc. Họ không lưu trữ bộ nhớ lâu hơn họ thường chạy vì họ sẽ thường xuyên gặp sự cố trong trường hợp này.

Thế giới thực

Bây giờ ví dụ trên đang sử dụng một sơ đồ đơn giản đến nực cười. Một ứng dụng trong thế giới thực có thể yêu cầu hàng ngàn hình ảnh được ghép lại với nhau để bao quát toàn bộ biểu đồ, với hàng trăm loại tài nguyên khác nhau được lưu trữ trong biểu đồ cảnh, tài nguyên GPU được liên kết với một số trong số chúng, máy gia tốc gắn với người khác, người quan sát được phân phối trên hàng trăm plugin xem một số loại thực thể trong cảnh để thay đổi, người quan sát quan sát người quan sát, âm thanh được đồng bộ hóa với hình ảnh động, v.v ... Vì vậy, có vẻ như thật dễ dàng để tránh sai lầm mà tôi đã mô tả ở trên, nhưng nói chung không có gì đơn giản trong thế giới thực này cơ sở sản xuất cho một ứng dụng phức tạp trải dài hàng triệu dòng mã.

Cơ hội mà một người nào đó, một ngày nào đó, sẽ quản lý tài nguyên ở đâu đó trong cơ sở mã đó có xu hướng khá cao và xác suất đó là như nhau có hoặc không có GC. Sự khác biệt chính là những gì sẽ xảy ra do lỗi này, điều này cũng ảnh hưởng đến khả năng ảnh hưởng của lỗi này sẽ được phát hiện và sửa chữa nhanh như thế nào.

Tai nạn so với rò rỉ

Bây giờ cái nào tệ hơn? Một vụ tai nạn ngay lập tức, hoặc rò rỉ ký ức im lặng nơi Joe chỉ lẩn quẩn một cách bí ẩn?

Hầu hết có thể trả lời câu hỏi sau, nhưng giả sử phần mềm này được thiết kế để chạy hàng giờ liên tục, có thể là vài ngày và mỗi phần mềm của Joe và Jane chúng tôi thêm vào sẽ tăng mức sử dụng bộ nhớ của phần mềm lên một gigabyte. Đây không phải là một phần mềm quan trọng cho nhiệm vụ (sự cố không thực sự giết chết người dùng), mà là một phần mềm quan trọng về hiệu năng.

Trong trường hợp này, một sự cố nghiêm trọng xuất hiện ngay lập tức khi gỡ lỗi, chỉ ra lỗi bạn mắc phải, thực sự có thể tốt hơn là chỉ là một phần mềm bị rò rỉ thậm chí có thể bay theo radar của quy trình thử nghiệm của bạn.

Mặt khác, nếu đó là một phần mềm quan trọng trong nhiệm vụ mà hiệu suất không phải là mục tiêu, chỉ cần không bị sập bởi bất kỳ phương tiện nào có thể, thì thực sự có thể rò rỉ.

Tài liệu tham khảo yếu

Có một loại lai giữa những ý tưởng này có sẵn trong các sơ đồ GC được gọi là tài liệu tham khảo yếu. Với các tài liệu tham khảo yếu, chúng ta có thể có tất cả các tổ chức này Joe tham chiếu yếu nhưng không ngăn anh ta bị xóa khi tham chiếu mạnh (chủ sở hữu / huyết mạch của Joe) biến mất. Tuy nhiên, chúng tôi nhận được lợi ích của việc có thể phát hiện khi Joe không còn ở đây thông qua các tài liệu tham khảo yếu này, cho phép chúng tôi nhận được một loại lỗi dễ tái tạo.

Thật không may, các tài liệu tham khảo yếu không được sử dụng nhiều như chúng có thể được sử dụng, do đó, rất nhiều ứng dụng GC phức tạp có thể dễ bị rò rỉ ngay cả khi chúng có khả năng ít bị hỏng hơn ứng dụng C phức tạp, ví dụ

Trong mọi trường hợp, việc GC có làm cho cuộc sống của bạn dễ dàng hơn hay khó khăn hơn hay không phụ thuộc vào mức độ quan trọng của phần mềm của bạn để tránh rò rỉ, và liệu nó có liên quan đến việc quản lý tài nguyên phức tạp kiểu này hay không.

Trong trường hợp của tôi, tôi làm việc trong một lĩnh vực quan trọng về hiệu năng, nơi các tài nguyên trải rộng từ hàng trăm megabyte đến gigabyte và không giải phóng bộ nhớ đó khi người dùng yêu cầu giải nén vì một lỗi như trên thực sự có thể ít gặp sự cố hơn. Các sự cố rất dễ phát hiện và tái tạo, khiến chúng thường là loại lỗi yêu thích của lập trình viên, ngay cả khi đó là lỗi yêu thích nhất của người dùng và rất nhiều sự cố này sẽ xuất hiện với quy trình kiểm tra lành mạnh trước khi chúng đến tay người dùng.

Dù sao, đó là những khác biệt giữa quản lý bộ nhớ thủ công và GC. Để trả lời câu hỏi ngay lập tức của bạn, tôi sẽ nói rằng quản lý bộ nhớ thủ công là khó khăn, nhưng nó rất ít liên quan đến rò rỉ, và cả hai hình thức quản lý bộ nhớ thủ công và GC vẫn rất khó khăn khi quản lý tài nguyên không tầm thường. Người ta cho rằng GC có nhiều hành vi phức tạp hơn ở đây khi chương trình dường như hoạt động tốt nhưng lại ngày càng tiêu tốn nhiều tài nguyên hơn. Các hình thức thủ công là ít khó khăn hơn, nhưng sẽ sụp đổ và đốt thời gian lớn với những sai lầm như hình trên.


-1

Dưới đây là danh sách các vấn đề mà các lập trình viên C ++ gặp phải khi xử lý bộ nhớ:

  1. Vấn đề phạm vi xảy ra trong bộ nhớ được cấp phát ngăn xếp: thời gian tồn tại bên ngoài của chức năng được phân bổ. Có ba giải pháp chính cho vấn đề này: bộ nhớ heap và di chuyển điểm phân bổ lên trên trong ngăn xếp cuộc gọi hoặc phân bổ từ bên trong các đối tượng .
  2. Vấn đề về sizeof nằm trong ngăn xếp được phân bổ và phân bổ từ bên trong đối tượng và bộ nhớ được phân bổ một phần heap: Kích thước của khối bộ nhớ không thể thay đổi trong thời gian chạy. Giải pháp là mảng bộ nhớ heap, con trỏ và thư viện và container.
  3. Thứ tự của vấn đề định nghĩa là khi phân bổ từ bên trong các đối tượng: các lớp bên trong chương trình cần phải theo đúng thứ tự. Các giải pháp đang hạn chế sự phụ thuộc vào một cây và sắp xếp lại các lớp và không sử dụng khai báo chuyển tiếp, con trỏ và bộ nhớ heap và sử dụng khai báo chuyển tiếp.
  4. Vấn đề bên trong-bên ngoài là trong bộ nhớ được cấp phát đối tượng. Truy cập bộ nhớ bên trong các đối tượng được chia thành hai phần, một số bộ nhớ nằm trong một đối tượng và bộ nhớ khác nằm ngoài nó và các lập trình viên cần chọn chính xác để sử dụng bố cục hoặc tham chiếu dựa trên quyết định này. Các giải pháp đang thực hiện quyết định chính xác, hoặc con trỏ và bộ nhớ heap.
  5. Vấn đề đối tượng đệ quy là trong bộ nhớ được cấp phát đối tượng. Kích thước của các đối tượng trở nên vô hạn nếu cùng một đối tượng được đặt bên trong chính nó và các giải pháp là các tham chiếu, bộ nhớ heap và các con trỏ.
  6. Vấn đề theo dõi quyền sở hữu là trong bộ nhớ được phân bổ heap, con trỏ chứa địa chỉ của bộ nhớ được phân bổ heap phải được chuyển từ điểm phân bổ đến điểm phân bổ. Các giải pháp là bộ nhớ được cấp phát ngăn xếp, bộ nhớ alloated đối tượng, auto_ptr, shared_ptr, unique_ptr, stdlib container.
  7. Vấn đề sao chép quyền sở hữu nằm trong bộ nhớ được phân bổ heap: việc phân bổ chỉ có thể được thực hiện một lần. Các giải pháp là bộ nhớ được cấp phát ngăn xếp, bộ nhớ cấp phát đối tượng, auto_ptr, shared_ptr, unique_ptr, stdlib container.
  8. Vấn đề con trỏ Null nằm trong bộ nhớ được phân bổ heap: các con trỏ được phép là NULL làm cho nhiều hoạt động bị lỗi trong thời gian chạy. Giải pháp là bộ nhớ ngăn xếp, bộ nhớ phân bổ đối tượng và phân tích cẩn thận các vùng heap và tham chiếu.
  9. Vấn đề rò rỉ bộ nhớ nằm trong bộ nhớ được cấp phát heap: Quên cuộc gọi xóa cho mọi khối bộ nhớ được phân bổ. Giải pháp là các công cụ như valgrind.
  10. Vấn đề tràn ngăn xếp là cho các cuộc gọi hàm đệ quy đang sử dụng bộ nhớ ngăn xếp. Thông thường kích thước của ngăn xếp được xác định hoàn toàn vào thời gian biên dịch, ngoại trừ trường hợp thuật toán đệ quy. Việc xác định kích thước ngăn xếp của hệ điều hành sai cũng thường gây ra vấn đề này vì không có cách nào để đo kích thước không gian ngăn xếp cần thiết.

Như bạn có thể thấy, bộ nhớ heap đang giải quyết rất nhiều vấn đề hiện có, nhưng nó gây ra sự phức tạp bổ sung. GC được thiết kế để xử lý một phần của sự phức tạp đó. (xin lỗi nếu một số tên vấn đề không phải là tên chính xác cho những vấn đề này - đôi khi rất khó để tìm ra tên chính xác)


1
-1: Không phải là một câu trả lời cho câu hỏi.
Sjoerd
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.