Tại sao C ++ không có trình thu gom rác?


270

Tôi không hỏi câu hỏi này vì ưu điểm của việc thu gom rác trước hết. Lý do chính của tôi để hỏi điều này là tôi biết rằng Bjarne Stroustrup đã nói rằng C ++ sẽ có một trình thu gom rác tại một số thời điểm.

Như đã nói, tại sao nó không được thêm vào? Hiện đã có một số trình thu gom rác cho C ++. Đây có phải chỉ là một trong những kiểu "nói dễ hơn làm" không? Hoặc có những lý do nào khác mà nó chưa được thêm vào (và sẽ không được thêm vào trong C ++ 11)?

Liên kết chéo:

Chỉ cần làm rõ, tôi hiểu lý do tại sao C ++ không có trình thu gom rác khi nó được tạo lần đầu tiên. Tôi đang tự hỏi tại sao bộ sưu tập không thể được thêm vào.


26
Đây là một trong mười huyền thoại hàng đầu về C ++ mà những kẻ thù ghét luôn đưa ra. Bộ sưu tập rác không được "tích hợp", nhưng có một số cách dễ dàng để làm điều đó C ++. Đăng một bình luận vì những người khác đã trả lời tốt hơn tôi có thể dưới đây :)
davr

5
Nhưng đó là toàn bộ vấn đề về việc không được tích hợp sẵn, bạn phải tự làm điều đó. Tính khả thi từ cao đến thấp: tích hợp, thư viện, làm tại nhà. Bản thân tôi sử dụng C ++ và chắc chắn không phải là người ghét vì đó là ngôn ngữ tốt nhất trên thế giới. Nhưng quản lý bộ nhớ năng động là một nỗi đau.
QBziZ

4
@Davr - Tôi không phải là người ghét C ++ và thậm chí tôi còn cố gắng tranh luận rằng C ++ cần một người thu gom rác. Tôi đang hỏi bởi vì tôi biết rằng Bjarne Stroustrup đã nói rằng nó S be được thêm vào và chỉ tò mò lý do không thực hiện nó là gì.
Jason Baker

1
Bài viết này Trình thu thập Boehm cho C và C ++ của Tiến sĩ Dobbs mô tả một trình thu gom rác nguồn mở có thể được sử dụng với cả C và C ++. Nó thảo luận về một số vấn đề phát sinh khi sử dụng trình thu gom rác với các hàm hủy C ++ cũng như Thư viện chuẩn C.
Richard Chambers

1
@rogerdpack: Nhưng bây giờ nó không hữu ích lắm (xem câu trả lời của tôi ...) vì vậy việc triển khai không chắc sẽ đầu tư vào việc có một cái.
einpoklum

Câu trả lời:


160

Bộ sưu tập rác ngầm có thể đã được thêm vào, nhưng nó không thực hiện việc cắt giảm. Có lẽ do không chỉ các biến chứng thực hiện, mà còn do mọi người không thể đi đến một sự đồng thuận chung đủ nhanh.

Một trích dẫn từ chính Bjarne Stroustrup:

Tôi đã hy vọng rằng một trình thu gom rác có thể được kích hoạt tùy chọn sẽ là một phần của C ++ 0x, nhưng có đủ các vấn đề kỹ thuật mà tôi phải thực hiện chỉ với một đặc tả chi tiết về cách một trình thu thập như vậy tích hợp với phần còn lại của ngôn ngữ , Nếu được cung cấp. Như trường hợp với tất cả các tính năng C ++ 0x, một triển khai thử nghiệm tồn tại.

Có một cuộc thảo luận tốt về chủ đề ở đây .

Tổng quan chung:

C ++ rất mạnh mẽ và cho phép bạn làm hầu hết mọi thứ. Vì lý do này, nó không tự động đẩy nhiều thứ lên bạn có thể ảnh hưởng đến hiệu suất. Việc thu gom rác có thể được thực hiện dễ dàng với các con trỏ thông minh (các đối tượng bao bọc các con trỏ với số tham chiếu, tự động xóa khi số tham chiếu đạt 0).

C ++ được xây dựng với các đối thủ cạnh tranh không có bộ sưu tập rác. Hiệu quả là mối quan tâm chính mà C ++ phải chống lại sự chỉ trích so với C và những người khác.

Có 2 loại thu gom rác ...

Thu gom rác rõ ràng:

C ++ 0x sẽ có bộ sưu tập rác thông qua các con trỏ được tạo bằng shared_ptr

Nếu bạn muốn, bạn có thể sử dụng nó, nếu bạn không muốn, bạn không bị buộc phải sử dụng nó.

Hiện tại bạn cũng có thể sử dụng boost: shared_ptr nếu bạn không muốn đợi C ++ 0x.

Thu gom rác ngầm:

Nó không có bộ sưu tập rác trong suốt. Nó sẽ là một điểm tập trung cho các thông số kỹ thuật C ++ trong tương lai.

Tại sao Tr1 không có bộ sưu tập rác ngầm?

Có rất nhiều thứ mà tr1 của C ++ 0x nên có, Bjarne Stroustrup trong các cuộc phỏng vấn trước đây đã nói rằng tr1 không có nhiều như anh ta muốn.


71
Tôi sẽ trở thành một người đáng ghét nếu C ++ bắt buộc phải thu gom rác! Tại sao mọi người không thể sử dụng smart_ptr's? Làm thế nào bạn có thể thực hiện chuyển đổi phong cách Unix cấp thấp, với một trình thu gom rác theo cách? Những thứ khác sẽ bị ảnh hưởng như luồng. Python có khóa trình thông dịch toàn cầu chủ yếu là do bộ sưu tập rác của nó (xem Cython). Giữ nó ra khỏi C / C ++, cảm ơn.
unixman83

26
@ unixman83: Vấn đề chính với bộ sưu tập rác được tính tham chiếu (nghĩa là std::shared_ptr) là các tham chiếu theo chu kỳ, gây rò rỉ bộ nhớ. Do đó, bạn phải cẩn thận sử dụng std::weak_ptrđể phá vỡ các chu kỳ, đó là lộn xộn. Đánh dấu và quét kiểu GC không có vấn đề này. Không có sự không tương thích vốn có giữa luồng / rèn và thu gom rác. Cả Java và C # đều có đa luồng được ưu tiên hiệu suất cao và trình thu gom rác. Có những vấn đề phải làm với các ứng dụng thời gian thực và trình thu gom rác, vì hầu hết những người thu gom rác phải dừng thế giới để chạy.
Andrew Tomazos

9
"Vấn đề chính với bộ sưu tập rác được tính tham chiếu (nghĩa là std::shared_ptr) là tham chiếu theo chu kỳ" và hiệu suất khủng khiếp là mỉa mai vì hiệu suất tốt hơn thường là sự biện minh cho việc sử dụng C ++ ... Flyingfrogblog.blogspot.co.uk/2011/01/ Khăn
Jon Harrop

11
"Làm thế nào bạn có thể thực hiện phong cách Unix cấp thấp". Giống như các ngôn ngữ của GC như OCaml đã làm điều đó trong khoảng 20 năm trở lên.
Jon Harrop

9
"Python có khóa trình thông dịch toàn cầu chủ yếu là do bộ sưu tập rác". Người rơm tranh luận. Cả Java và .NET đều có GC nhưng không có khóa toàn cầu.
Jon Harrop

149

Để thêm vào cuộc tranh luận ở đây.

Có những vấn đề đã biết với bộ sưu tập rác và việc hiểu chúng giúp hiểu tại sao không có gì trong C ++.

1. Hiệu suất?

Khiếu nại đầu tiên thường là về hiệu suất, nhưng hầu hết mọi người không thực sự nhận ra những gì họ đang nói. Như được minh họa bởi Martin Beckettvấn đề có thể không phải là hiệu suất mỗi lần, mà là khả năng dự đoán về hiệu suất.

Hiện tại có 2 họ của GC được triển khai rộng rãi:

  • Mark-And-Sweep
  • Loại đếm tham chiếu

Tốc Mark And Sweepđộ nhanh hơn (ít ảnh hưởng đến hiệu suất tổng thể) nhưng nó mắc phải hội chứng "đóng băng thế giới": tức là khi GC khởi động, mọi thứ khác sẽ dừng lại cho đến khi GC dọn dẹp. Nếu bạn muốn xây dựng một máy chủ trả lời trong vài mili giây ... một số giao dịch sẽ không đáp ứng mong đợi của bạn :)

Vấn đề Reference Countinglà khác nhau: đếm tham chiếu thêm chi phí, đặc biệt là trong môi trường Đa luồng vì bạn cần phải có số nguyên tử. Hơn nữa, có vấn đề về chu kỳ tham chiếu, do đó bạn cần một thuật toán thông minh để phát hiện các chu trình đó và loại bỏ chúng (thường được thực hiện bằng cách "đóng băng thế giới", mặc dù ít thường xuyên hơn). Nói chung, như ngày nay, loại này (mặc dù thông thường phản ứng nhanh hơn hoặc đúng hơn, đóng băng ít thường xuyên hơn) là chậm hơn so với Mark And Sweep.

Tôi đã thấy một bài báo của những người thực hiện Eiffel đang cố gắng thực hiện Trình Reference Countingthu gom rác có hiệu suất toàn cầu tương tự Mark And Sweepmà không có khía cạnh "Đóng băng thế giới". Nó đòi hỏi một luồng riêng cho GC (điển hình). Thuật toán này hơi đáng sợ (cuối cùng), nhưng bài báo đã thực hiện tốt việc giới thiệu các khái niệm cùng một lúc và cho thấy sự tiến hóa của thuật toán từ phiên bản "đơn giản" sang phiên bản đầy đủ. Đề nghị đọc nếu chỉ tôi có thể đặt tay trở lại vào tệp PDF ...

2. Thu thập tài nguyên là khởi tạo (RAII)

Đó là một thành ngữ phổ biến ở C++chỗ bạn sẽ bao bọc quyền sở hữu tài nguyên trong một đối tượng để đảm bảo rằng chúng được phát hành đúng. Nó chủ yếu được sử dụng cho bộ nhớ vì chúng tôi không có bộ sưu tập rác, nhưng dù sao nó cũng hữu ích cho nhiều tình huống khác:

  • khóa (đa luồng, xử lý tệp, ...)
  • kết nối (đến cơ sở dữ liệu, máy chủ khác, ...)

Ý tưởng là kiểm soát đúng thời gian tồn tại của đối tượng:

  • nó sẽ sống miễn là bạn cần nó
  • nó sẽ bị giết khi bạn hoàn thành nó

Vấn đề của GC là nếu nó giúp với cái trước và cuối cùng đảm bảo rằng sau này ... cái "tối thượng" này có thể không đủ. Nếu bạn phát hành một khóa, bạn thực sự muốn nó được phát hành ngay bây giờ, để nó không chặn bất kỳ cuộc gọi nào nữa!

Ngôn ngữ với GC có hai cách giải quyết:

  • không sử dụng GC khi phân bổ ngăn xếp là đủ: thông thường đối với các vấn đề về hiệu năng, nhưng trong trường hợp của chúng tôi, nó thực sự có ích vì phạm vi xác định thời gian tồn tại
  • usingxây dựng ... nhưng RAII rõ ràng (yếu) trong khi ở C ++ RAII ẩn để người dùng KHÔNG THỂ vô tình mắc lỗi (bằng cách bỏ qua usingtừ khóa)

3. Con trỏ thông minh

Con trỏ thông minh thường xuất hiện dưới dạng viên đạn bạc để xử lý bộ nhớ trong C++. Thường thì tôi đã nghe nói: chúng ta không cần đến GC, vì chúng ta có con trỏ thông minh.

Người ta không thể sai nhiều hơn.

Con trỏ thông minh giúp ích: auto_ptrunique_ptrsử dụng các khái niệm RAII, thực sự rất hữu ích. Chúng đơn giản đến mức bạn có thể tự viết chúng khá dễ dàng.

Tuy nhiên, khi một người cần chia sẻ quyền sở hữu sẽ khó khăn hơn: bạn có thể chia sẻ giữa nhiều luồng và có một vài vấn đề tinh tế với việc xử lý số đếm. Do đó, một cách tự nhiên đi về phía shared_ptr.

Thật tuyệt, đó là thứ Boost cho, nhưng nó không phải là viên đạn bạc. Trong thực tế, vấn đề chính shared_ptrlà nó mô phỏng một GC được thực hiện bởi Reference Countingnhưng bạn cần phải tự mình thực hiện phát hiện chu trình ... Urg

Tất nhiên là có điều này weak_ptr, nhưng tôi không may đã thấy rò rỉ bộ nhớ mặc dù đã sử dụng shared_ptrvì những chu kỳ đó ... và khi bạn ở trong môi trường Đa luồng, rất khó phát hiện!

4. Giải pháp là gì?

Không có viên đạn bạc, nhưng như mọi khi, nó chắc chắn khả thi. Trong trường hợp không có GC, người ta cần phải rõ ràng về quyền sở hữu:

  • thích có một chủ sở hữu tại một thời điểm nhất định, nếu có thể
  • nếu không, hãy chắc chắn rằng sơ đồ lớp của bạn không có bất kỳ chu kỳ nào liên quan đến quyền sở hữu và phá vỡ chúng bằng ứng dụng tinh tế của weak_ptr

Vì vậy, thực sự sẽ rất tuyệt nếu có một GC ... tuy nhiên đó không phải là vấn đề nhỏ. Và trong lúc này, chúng ta chỉ cần xắn tay áo lên.


2
Tôi ước tôi có thể chấp nhận hai câu trả lời! Điều này thật tuyệt vời. Một điều cần chỉ ra, liên quan đến hiệu năng, GC chạy trong một luồng riêng biệt thực sự khá phổ biến (nó được sử dụng trong Java và .Net). Cấp, điều đó có thể không được chấp nhận trong các hệ thống nhúng.
Jason Baker

14
Chỉ có hai loại? Làm thế nào 'bout sao chép bộ sưu tập? Nhà sưu tập thế hệ? Các nhà sưu tập đồng thời (bao gồm cả máy chạy bộ thời gian thực cứng của Baker)? Thu thập lai khác nhau? Đôi khi, sự thiếu hiểu biết trong ngành công nghiệp của lĩnh vực này đôi khi làm tôi kinh ngạc.
CHỈ CẦN HOẠT ĐỘNG CỦA TÔI NGÀY

12
Tôi đã nói chỉ có 2 loại? Tôi nói rằng có 2 đã được triển khai rộng rãi. Theo như tôi biết Python, Java và C # đều sử dụng thuật toán Mark và Sweep (Java từng có thuật toán đếm tham chiếu). Nói chính xác hơn, đối với tôi, C # sử dụng GC thế hệ cho các chu kỳ nhỏ, Mark And Sweep cho các chu kỳ chính và Sao chép để chống phân mảnh bộ nhớ; mặc dù tôi sẽ tranh luận rằng trung tâm của thuật toán là Mark And Sweep. Bạn có biết ngôn ngữ chính nào sử dụng công nghệ khác không? Tôi luôn vui vẻ học hỏi.
Matthieu M.

3
Bạn chỉ cần đặt tên một ngôn ngữ chính sử dụng ba ngôn ngữ.
CHỈ CẦN HOẠT ĐỘNG CỦA TÔI NGÀY

3
Sự khác biệt chính là GC thế hệ và tăng dần không cần ngăn thế giới hoạt động và bạn có thể làm cho chúng hoạt động trên các hệ thống đơn luồng mà không cần quá nhiều chi phí bằng cách thỉnh thoảng thực hiện một lần lặp của giao dịch cây khi truy cập vào con trỏ GC (hệ số có thể được xác định bởi số lượng nút mới, cùng với dự đoán cơ bản về nhu cầu thu thập). Bạn có thể đưa GC đi xa hơn bằng cách bao gồm dữ liệu về nơi tạo mã / sửa đổi nút, điều này có thể cho phép bạn cải thiện dự đoán của mình và bạn có được Phân tích Thoát miễn phí với nó.
Keldon Alleyne

56

Loại? Có nên tối ưu hóa cho bộ điều khiển máy giặt nhúng, điện thoại di động, máy trạm hoặc siêu máy tính?
Nó nên ưu tiên gui đáp ứng hoặc tải máy chủ?
Nó nên sử dụng nhiều bộ nhớ hay nhiều CPU?

C / c ++ được sử dụng trong quá nhiều trường hợp khác nhau. Tôi nghi ngờ một cái gì đó như tăng con trỏ thông minh sẽ đủ cho hầu hết người dùng

Chỉnh sửa - Trình thu gom rác tự động không phải là vấn đề về hiệu suất (bạn luôn có thể mua thêm máy chủ) đó là câu hỏi về hiệu suất có thể dự đoán được.
Không biết khi nào thì GC sẽ khởi động giống như thuê một phi công hàng không ma túy, phần lớn thời gian họ rất tuyệt - nhưng khi bạn thực sự cần sự đáp ứng!


6
Tôi chắc chắn thấy quan điểm của bạn, nhưng tôi cảm thấy buộc phải hỏi: không phải Java được sử dụng trong gần như nhiều ứng dụng sao?
Jason Baker

35
Không. Java không phù hợp với các ứng dụng hiệu suất cao, vì lý do đơn giản là nó không có đảm bảo hiệu năng ở cùng mức độ với C ++. Vì vậy, bạn sẽ tìm thấy nó trong một chiếc điện thoại di động, nhưng bạn sẽ không tìm thấy nó trong một công tắc di động hoặc siêu máy tính.
Zathrus

11
Bạn luôn có thể mua thêm máy chủ, nhưng bạn không thể luôn mua thêm CPU cho điện thoại di động đã có sẵn trong túi của khách hàng!
Crashworks

8
Java đã thực hiện rất nhiều hoạt động bắt kịp hiệu suất CPU. Vấn đề thực sự khó hiểu là việc sử dụng bộ nhớ, Java vốn ít hiệu quả bộ nhớ hơn C ++. Và sự kém hiệu quả đó là do thực tế là rác được thu gom. Bộ sưu tập rác không thể nhanh và hiệu quả bộ nhớ, một thực tế sẽ trở nên rõ ràng nếu bạn xem xét các thuật toán GC hoạt động nhanh như thế nào.
Nate CK

2
@Zathrus java có thể giành chiến thắng với thông lượng b / c của jit tối ưu hóa, mặc dù không có độ trễ (boo real-time) và chắc chắn không phải là dấu chân bộ nhớ.
gtrak

34

Một trong những lý do lớn nhất mà C ++ không được xây dựng trong bộ sưu tập rác là việc thu gom rác để chơi tốt với các công cụ hủy diệt thực sự rất khó khăn. Theo tôi biết, không ai thực sự biết làm thế nào để giải quyết nó hoàn toàn. Có rất nhiều vấn đề cần giải quyết:

  • tuổi thọ xác định của các đối tượng (đếm tham chiếu cung cấp cho bạn điều này, nhưng GC thì không. Mặc dù nó có thể không phải là vấn đề lớn).
  • Điều gì xảy ra nếu một hàm hủy ném khi đối tượng đang được thu gom rác? Hầu hết các ngôn ngữ đều bỏ qua ngoại lệ này, vì thực sự không có khối bắt để có thể vận chuyển nó đến, nhưng đây có lẽ không phải là một giải pháp chấp nhận được cho C ++.
  • Làm thế nào để kích hoạt / vô hiệu hóa nó? Đương nhiên, đó có thể là một quyết định biên dịch thời gian, nhưng mã được viết cho GC so với mã được viết cho KHÔNG phải là GC sẽ rất khác nhau và có thể không tương thích. Làm thế nào để bạn hòa giải này?

Đây chỉ là một vài trong số các vấn đề phải đối mặt.


17
GC và tàu khu trục là một vấn đề được giải quyết, bởi một sidestep tốt đẹp từ Bjarne. Các cấu trúc hủy không chạy trong GC, vì đó không phải là điểm của GC. GC trong C ++ tồn tại để tạo ra khái niệm về bộ nhớ vô hạn , không phải vô hạn các tài nguyên khác.
MSalters

2
Nếu các hàm hủy không chạy mà thay đổi hoàn toàn ngữ nghĩa của ngôn ngữ. Tôi đoán ít nhất bạn sẽ cần một từ khóa mới "gcnew" hoặc một cái gì đó để bạn rõ ràng cho phép đối tượng này được sử dụng (và do đó bạn không nên sử dụng nó để bọc tài nguyên bên cạnh bộ nhớ).
Greg Rogers

7
Đây là một lập luận không có thật. Vì C ++ có quản lý bộ nhớ rõ ràng, bạn cần tìm ra khi nào mọi đối tượng phải được giải phóng. Với GC, nó không tệ hơn; thay vào đó, vấn đề được giảm xuống để tìm ra khi một số đối tượng nhất định được giải phóng, cụ thể là những đối tượng cần xem xét đặc biệt khi xóa. Kinh nghiệm lập trình trong Java và C # cho thấy rằng phần lớn các đối tượng không yêu cầu cân nhắc đặc biệt và có thể được để lại một cách an toàn cho GC. Hóa ra, một trong những chức năng chính của các hàm hủy trong C ++ là giải phóng các đối tượng con, mà GC tự động xử lý cho bạn.
Nate CK

2
@ NateC-K: Một điều được cải thiện trong GC so với không phải GC (có lẽ là điều lớn nhất) là khả năng một hệ thống GC vững chắc đảm bảo rằng mọi tham chiếu sẽ tiếp tục trỏ đến cùng một đối tượng miễn là tham chiếu tồn tại. Gọi Disposemột đối tượng có thể làm cho nó không thể thực hiện được, nhưng các tham chiếu chỉ đến đối tượng khi nó còn sống sẽ tiếp tục làm như vậy sau khi nó chết. Ngược lại, trong các hệ thống không phải là GC, các đối tượng có thể bị xóa trong khi các tham chiếu tồn tại và hiếm khi có bất kỳ giới hạn nào đối với sự tàn phá có thể bị phá hủy nếu một trong những tham chiếu đó được sử dụng.
supercat

22

Mặc dù đây là một câu hỏi , nhưng vẫn còn một vấn đề mà tôi không thấy bất kỳ ai đã giải quyết cả: bộ sưu tập rác hầu như không thể chỉ định.

Cụ thể, tiêu chuẩn C ++ khá cẩn thận để chỉ định ngôn ngữ theo các hành vi có thể quan sát được bên ngoài, thay vì cách triển khai đạt được hành vi đó. Tuy nhiên, trong trường hợp thu gom rác thải, có hầu như không có bên ngoài hành vi quan sát được.

Các ý tưởng chung của thu gom rác thải là nó nên thực hiện một nỗ lực hợp lý tại đảm bảo rằng một cấp phát bộ nhớ sẽ thành công. Thật không may, về cơ bản không thể đảm bảo rằng mọi phân bổ bộ nhớ sẽ thành công, ngay cả khi bạn có bộ thu gom rác hoạt động. Điều này đúng ở một mức độ nào đó trong mọi trường hợp, nhưng đặc biệt là trong trường hợp của C ++, bởi vì (có lẽ) không thể sử dụng bộ thu sao chép (hoặc bất cứ thứ gì tương tự) để di chuyển các đối tượng trong bộ nhớ trong chu kỳ thu thập.

Nếu bạn không thể di chuyển các đối tượng, bạn không thể tạo một không gian bộ nhớ đơn, liền kề để phân bổ - và điều đó có nghĩa là heap của bạn (hoặc cửa hàng miễn phí, hoặc bất cứ điều gì bạn muốn gọi nó) có thể, và có thể sẽ , trở nên rời rạc theo thời gian. Điều này, đến lượt nó, có thể ngăn việc phân bổ thành công, ngay cả khi có nhiều bộ nhớ hơn số lượng được yêu cầu.

Mặc dù có thể đưa ra một số đảm bảo rằng (về bản chất) rằng nếu bạn lặp lại chính xác cùng một kiểu phân bổ và nó đã thành công lần đầu tiên, nó sẽ tiếp tục thành công ở các lần lặp tiếp theo, với điều kiện là bộ nhớ được phân bổ không thể truy cập giữa các lần lặp. Đó là một sự đảm bảo yếu như vậy về cơ bản nó vô dụng, nhưng tôi không thể thấy bất kỳ hy vọng hợp lý nào để củng cố nó.

Mặc dù vậy, nó mạnh hơn những gì đã được đề xuất cho C ++. Các đề xuất trước đây [cảnh báo: PDF] (đó đã giảm) thì không gì đảm bảo cả. Trong 28 trang đề xuất, những gì bạn có trong cách hành xử có thể quan sát được bên ngoài là một ghi chú (không quy tắc) có nội dung:

[Lưu ý: Đối với các chương trình được thu gom rác, việc triển khai được lưu trữ chất lượng cao nên cố gắng tối đa hóa lượng bộ nhớ không thể truy cập mà nó thu hồi được. Lưu ý

Ít nhất là đối với tôi, điều này đặt ra một câu hỏi nghiêm túc về lợi tức đầu tư. Chúng tôi sẽ phá vỡ mã hiện tại (không ai chắc chắn chính xác là bao nhiêu, nhưng chắc chắn là khá ít), đặt ra các yêu cầu mới đối với việc triển khai và các hạn chế mới đối với mã, và những gì chúng tôi nhận được hoàn toàn không có gì cả?

Ngay cả tốt nhất, những gì chúng ta nhận được là các chương trình, dựa trên thử nghiệm với Java , có thể sẽ cần bộ nhớ gấp sáu lần để chạy với tốc độ như hiện tại. Tồi tệ hơn, bộ sưu tập rác là một phần của Java ngay từ đầu - C ++ đặt ra nhiều hạn chế hơn đối với trình thu gom rác rằng nó gần như chắc chắn sẽ có tỷ lệ chi phí / lợi ích thậm chí còn tồi tệ hơn (ngay cả khi chúng tôi vượt xa những gì đề xuất được đảm bảo và cho rằng sẽ có một số lợi ích).

Tôi muốn tóm tắt tình huống một cách toán học: đây là một tình huống phức tạp. Như bất kỳ nhà toán học nào cũng biết, một số phức có hai phần: thực và ảo. Tôi nhận thấy rằng những gì chúng ta có ở đây là chi phí là có thật, nhưng lợi ích là (ít nhất là chủ yếu) là tưởng tượng.


Tôi sẽ khẳng định rằng ngay cả khi một người chỉ định rằng để hoạt động đúng, tất cả các đối tượng phải bị xóa và chỉ những đối tượng đã bị xóa mới đủ điều kiện để thu thập, hỗ trợ trình biên dịch cho bộ sưu tập rác theo dõi tham chiếu vẫn có thể hữu ích, vì ngôn ngữ như vậy có thể đảm bảo việc sử dụng một con trỏ bị xóa (tham chiếu) sẽ được đảm bảo để bẫy, thay vì gây ra Hành vi không xác định.
supercat

2
Ngay cả trong Java, GC không thực sự được chỉ định để làm bất cứ điều gì hữu ích AFAIK. Nó có thể gọi freecho bạn (trong đó tôi có nghĩa là nói freedối với ngôn ngữ C). Nhưng Java không bao giờ đảm bảo gọi các bộ hoàn thiện hoặc bất cứ thứ gì tương tự. Trong thực tế, C ++ làm nhiều hơn Java để chạy xung quanh ghi cơ sở dữ liệu, xử lý tệp xử lý, v.v. Java tuyên bố có "GC", nhưng các nhà phát triển Java phải gọi một cách tỉ mỉ close()mọi lúc và họ phải rất ý thức về quản lý tài nguyên, cẩn thận không gọi close()quá sớm hoặc quá muộn. C ++ giải phóng chúng ta khỏi điều đó. ... (Tiếp theo)
Aaron McDaid

2
.. bình luận của tôi một lúc trước không nhằm chỉ trích Java. Tôi chỉ quan sát rằng thuật ngữ "thu gom rác" là một thuật ngữ rất kỳ lạ - nó có nghĩa ít hơn nhiều so với mọi người nghĩ và do đó rất khó để thảo luận về nó mà không hiểu rõ ý nghĩa của nó.
Aaron McDaid

@AaronMcDaid Đúng là GC không giúp gì cho tài nguyên không có bộ nhớ. May mắn thay, tài nguyên như vậy được phân bổ khá hiếm khi so sánh với bộ nhớ. Hơn nữa, hơn 90% trong số chúng có thể được giải phóng trong phương thức phân bổ chúng, vì vậy hãy try (Whatever w=...) {...}giải quyết nó (và bạn nhận được cảnh báo khi bạn quên). Những cái còn lại cũng có vấn đề với RAII. Gọi close()"mọi lúc" có nghĩa là có thể một lần trên hàng chục nghìn dòng, vì vậy điều đó không tệ, trong khi bộ nhớ được phân bổ gần như trên mỗi dòng Java.
maaartinus

15

Nếu bạn muốn thu gom rác tự động, có những người thu gom rác thương mại và miền công cộng tốt cho C ++. Đối với các ứng dụng có bộ sưu tập rác phù hợp, C ++ là ngôn ngữ được thu gom rác tuyệt vời với hiệu suất so sánh thuận lợi với các ngôn ngữ được thu gom rác khác. Xem Ngôn ngữ lập trình C ++ (Phiên bản 4) để thảo luận về bộ sưu tập rác tự động trong C ++. Xem thêm, Hans-J. Trang web của Boehm cho bộ sưu tập rác C và C ++ ( lưu trữ ).

Ngoài ra, C ++ hỗ trợ các kỹ thuật lập trình cho phép quản lý bộ nhớ an toàn và ẩn mà không cần bộ thu gom rác . Tôi coi việc thu gom rác là lựa chọn cuối cùng và cách xử lý không hoàn hảo để quản lý tài nguyên. Điều đó không có nghĩa là nó không bao giờ hữu ích, chỉ là có những cách tiếp cận tốt hơn trong nhiều tình huống.

Nguồn: http://www.stroustrup.com/bs_faq.html#garbage-collection

Về lý do tại sao nó không được tích hợp sẵn, nếu tôi nhớ chính xác thì nó đã được phát minh trước khi GC là thứ đó và tôi không tin rằng ngôn ngữ có thể có GC vì một số lý do (IE Backwards tương thích với C)

Hi vọng điêu nay co ich.


"Với hiệu suất so sánh thuận lợi với các ngôn ngữ được thu gom rác khác". Trích dẫn?
Jon Harrop

1
Liên kết của tôi đã bị hỏng. Tôi đã viết câu trả lời này 5 năm trước.
Rayne

1
Ok, tôi đã hy vọng một số xác minh độc lập về những tuyên bố này, tức là không phải bởi Stroustrup hoặc Boehm. :-)
Jon Harrop

12

Stroustrup đã đưa ra một số nhận xét tốt về điều này tại hội nghị Đi bản địa 2013.

Chỉ cần bỏ qua khoảng 25m50 giây trong video này . (Tôi khuyên bạn nên xem toàn bộ video thực sự, nhưng điều này bỏ qua những thứ về bộ sưu tập rác.)

Khi bạn có một ngôn ngữ thực sự tuyệt vời giúp dễ dàng (và an toàn, và có thể dự đoán được, dễ đọc và dễ dạy) để đối phó với các đối tượng và giá trị theo cách trực tiếp, tránh sử dụng (rõ ràng) đống, sau đó bạn thậm chí không muốn thu gom rác.

Với C ++ hiện đại và những thứ chúng ta có trong C ++ 11, bộ sưu tập rác không còn được mong muốn ngoại trừ trong những trường hợp hạn chế. Trên thực tế, ngay cả khi một trình thu gom rác tốt được tích hợp vào một trong những trình biên dịch C ++ chính, tôi nghĩ rằng nó sẽ không được sử dụng thường xuyên. Sẽ dễ dàng hơn , không khó hơn, để tránh GC.

Ông cho thấy ví dụ này:

void f(int n, int x) {
    Gadget *p = new Gadget{n};
    if(x<100) throw SomeException{};
    if(x<200) return;
    delete p;
}

Điều này không an toàn trong C ++. Nhưng nó cũng không an toàn trong Java! Trong C ++, nếu hàm trả về sớm, deletesẽ không bao giờ được gọi. Nhưng nếu bạn có bộ sưu tập rác đầy đủ, chẳng hạn như trong Java, bạn chỉ nhận được một gợi ý rằng đối tượng sẽ bị hủy hoại "tại một thời điểm nào đó trong tương lai" ( Cập nhật: điều này thậm chí còn tệ hơn thế. Java khônghứa sẽ gọi cho người hoàn thiện bao giờ - nó có thể không bao giờ được gọi). Điều này không đủ tốt nếu Tiện ích giữ một tệp xử lý mở hoặc kết nối đến cơ sở dữ liệu hoặc dữ liệu mà bạn đã lưu vào bộ đệm để ghi vào cơ sở dữ liệu sau đó. Chúng tôi muốn Tiện ích bị hủy ngay khi hoàn thành, để giải phóng các tài nguyên này càng sớm càng tốt. Bạn không muốn máy chủ cơ sở dữ liệu của mình phải vật lộn với hàng ngàn kết nối cơ sở dữ liệu không còn cần thiết - không biết rằng chương trình của bạn đã hoạt động xong.

Vậy giải pháp là gì? Có một vài cách tiếp cận. Cách tiếp cận rõ ràng, mà bạn sẽ sử dụng cho phần lớn các đối tượng của mình là:

void f(int n, int x) {
    Gadget p = {n};  // Just leave it on the stack (where it belongs!)
    if(x<100) throw SomeException{};
    if(x<200) return;
}

Điều này cần ít ký tự để gõ. Nó không có cản newtrở. Nó không yêu cầu bạn gõ Gadgethai lần. Đối tượng bị phá hủy ở cuối hàm. Nếu đây là những gì bạn muốn, điều này rất trực quan. Gadgets hành xử giống như inthoặc double. Dự đoán, dễ đọc, dễ dạy. Mọi thứ đều là "giá trị". Đôi khi một giá trị lớn, nhưng các giá trị dễ dạy hơn bởi vì bạn không có 'hành động ở khoảng cách xa' mà bạn nhận được bằng con trỏ (hoặc tài liệu tham khảo).

Hầu hết các đối tượng bạn tạo ra chỉ được sử dụng trong hàm tạo ra chúng và có lẽ được chuyển làm đầu vào cho các hàm con. Lập trình viên không cần phải suy nghĩ về 'quản lý bộ nhớ' khi trả lại các đối tượng hoặc chia sẻ các đối tượng trên các phần được phân tách rộng rãi của phần mềm.

Phạm vi và tuổi thọ rất quan trọng. Hầu hết thời gian, sẽ dễ dàng hơn nếu thời gian tồn tại giống như phạm vi. Nó dễ hiểu và dễ dạy hơn. Khi bạn muốn một cuộc đời khác, rõ ràng là đọc mã mà bạn đang làm điều này, bằng cách sử dụng shared_ptrví dụ. (Hoặc trả lại các đối tượng (lớn) theo giá trị, tận dụng ngữ nghĩa di chuyển hoặc unique_ptr.

Điều này có vẻ như một vấn đề hiệu quả. Nếu tôi muốn trả lại một Tiện ích từ foo()thì sao? Ngữ nghĩa di chuyển của C ++ 11 giúp dễ dàng trả lại các đối tượng lớn. Chỉ cần viết Gadget foo() { ... }và nó sẽ chỉ hoạt động, và làm việc nhanh chóng. Bạn không cần phải tự làm phiền &&mình, chỉ cần trả lại mọi thứ theo giá trị và ngôn ngữ thường sẽ có thể thực hiện các tối ưu hóa cần thiết. (Ngay cả trước C ++ 03, trình biên dịch đã làm rất tốt trong việc tránh sao chép không cần thiết.)

Như Stroustrup đã nói ở những nơi khác trong video (diễn giải): "Chỉ có một nhà khoa học máy tính mới khăng khăng sao chép một vật thể, sau đó phá hủy bản gốc. (Khán giả cười). Tại sao không di chuyển vật thể trực tiếp đến vị trí mới? (không phải các nhà khoa học máy tính) mong đợi. "

Khi bạn có thể đảm bảo chỉ cần một bản sao của một đối tượng, việc hiểu được tuổi thọ của đối tượng sẽ dễ dàng hơn nhiều. Bạn có thể chọn chính sách trọn đời bạn muốn và bộ sưu tập rác ở đó nếu bạn muốn. Nhưng khi bạn hiểu được lợi ích của các phương pháp khác, bạn sẽ thấy rằng bộ sưu tập rác nằm ở cuối danh sách ưu tiên của bạn.

Nếu điều đó không hiệu quả với bạn, bạn có thể sử dụng unique_ptrhoặc không thành công shared_ptr. C ++ 11 được viết tốt là ngắn hơn, dễ đọc và dễ dạy hơn nhiều ngôn ngữ khác khi nói đến quản lý bộ nhớ.


1
Chỉ nên sử dụng GC cho các đối tượng không thu được tài nguyên (nghĩa là yêu cầu các thực thể khác thực hiện mọi việc thay mặt họ "cho đến khi có thông báo mới"). Nếu Gadgetkhông yêu cầu bất cứ điều gì khác làm thay mặt nó, mã gốc sẽ hoàn toàn an toàn trong Java nếu câu lệnh vô nghĩa (với Java) deletebị xóa.
supercat

@supercat, các đối tượng có hàm hủy nhàm chán rất thú vị. (Tôi chưa định nghĩa 'nhàm chán', nhưng về cơ bản là các hàm hủy không bao giờ cần phải gọi, ngoại trừ việc giải phóng bộ nhớ). Một trình biên dịch riêng lẻ có thể xử lý shared_ptr<T>đặc biệt khi T'nhàm chán'. Nó có thể quyết định không thực sự quản lý một bộ đếm ref cho loại đó, và thay vào đó sử dụng GC. Điều này sẽ cho phép sử dụng GC mà không cần nhà phát triển cần chú ý. Một shared_ptrcách đơn giản có thể được xem như là một con trỏ GC, cho phù hợp T. Nhưng có những hạn chế trong việc này, và nó sẽ làm cho nhiều chương trình chậm hơn.
Aaron McDaid

Một hệ thống loại tốt nên có các loại khác nhau cho các đối tượng heap do GC và RAII quản lý, vì một số mẫu sử dụng hoạt động rất tốt với cái này và rất kém với cái kia. Trong .NET hoặc Java, một tuyên bố string1=string2;sẽ thực hiện rất nhanh chóng không phụ thuộc vào chiều dài của chuỗi (đó là theo nghĩa đen không có gì hơn một tải đăng ký và đăng ký cửa hàng), và không yêu cầu bất kỳ khóa để đảm bảo rằng nếu tuyên bố trên được thực hiện trong khi string2đang được viết, string1sẽ giữ giá trị cũ hoặc giá trị mới, không có Hành vi không xác định).
supercat

Trong C ++, việc gán một shared_ptr<String>yêu cầu rất nhiều đồng bộ hóa phía sau hậu trường và việc gán một Stringcó thể hành xử kỳ quặc nếu một biến được đọc và ghi đồng thời. Các trường hợp người ta muốn viết và đọc Stringđồng thời không phổ biến lắm, nhưng có thể phát sinh nếu ví dụ một số mã muốn cung cấp báo cáo trạng thái liên tục cho các luồng khác. Trong .NET và Java, những thứ như vậy chỉ "hoạt động".
supercat

1
@cquilguy không có gì thay đổi, trừ khi bạn thực hiện các biện pháp phòng ngừa đúng đắn, Java vẫn cho phép trình hoàn thiện được gọi ngay khi hàm tạo hoàn thành. Dưới đây là một ví dụ thực tế: Hoàn thiện () đã gọi các đối tượng có thể truy cập mạnh mẽ trong Java 8 . Kết luận là không bao giờ sử dụng tính năng này, rằng hầu hết mọi người đều đồng ý là một lỗi thiết kế lịch sử của ngôn ngữ. Khi chúng ta làm theo lời khuyên đó, ngôn ngữ cung cấp tính quyết định mà chúng ta yêu thích.
Holger

11

Bởi vì C ++ hiện đại không cần bộ sưu tập rác.

Câu trả lời FAQ của Bjarne Stroustrup về vấn đề này cho biết :

Tôi không thích rác. Tôi không thích xả rác. Lý tưởng của tôi là loại bỏ sự cần thiết của người thu gom rác bằng cách không tạo ra bất kỳ rác nào. Điều đó bây giờ có thể.


Tình huống, đối với mã được viết trong những ngày này (C ++ 17 và tuân theo Nguyên tắc cốt lõi chính thức ) như sau:

  • Hầu hết các mã liên quan đến quyền sở hữu bộ nhớ đều nằm trong các thư viện (đặc biệt là các mã cung cấp các thùng chứa).
  • Hầu hết việc sử dụng mã liên quan đến quyền sở hữu bộ nhớ tuân theo mẫu RAII , do đó việc phân bổ được thực hiện khi xây dựng và phân bổ khi phá hủy, điều này xảy ra khi thoát khỏi phạm vi được phân bổ.
  • Bạn không phân bổ rõ ràng hoặc phân bổ bộ nhớ trực tiếp .
  • Con trỏ thô không sở hữu bộ nhớ (nếu bạn đã làm theo hướng dẫn), vì vậy bạn không thể rò rỉ bằng cách chuyển chúng xung quanh.
  • Nếu bạn đang tự hỏi làm thế nào bạn sẽ vượt qua các địa chỉ bắt đầu của chuỗi các giá trị trong bộ nhớ - bạn sẽ thực hiện điều đó với một khoảng ; không cần con trỏ thô.
  • Nếu bạn thực sự cần một "con trỏ" sở hữu, bạn sử dụng các con trỏ thông minh trong thư viện tiêu chuẩn của C ++ - chúng không thể rò rỉ và có hiệu quả cao (mặc dù ABI có thể cản trở điều đó). Ngoài ra, bạn có thể vượt qua quyền sở hữu qua các ranh giới phạm vi với "con trỏ chủ sở hữu" . Đây là không phổ biến và phải được sử dụng rõ ràng; nhưng khi được thông qua - chúng cho phép kiểm tra tĩnh tốt chống rò rỉ.

"Ồ vâng? Nhưng còn ...

... nếu tôi chỉ viết mã theo cách chúng ta thường viết C ++ ngày xưa? "

Thật vậy, bạn chỉ có thể bỏ qua tất cả các hướng dẫn và viết mã ứng dụng bị rò rỉ - và nó sẽ biên dịch và chạy (và rò rỉ), giống như mọi khi.

Nhưng đó không phải là một tình huống "đừng làm vậy", nơi mà nhà phát triển được kỳ vọng là có đạo đức và kiểm soát bản thân rất nhiều; Nó không đơn giản để viết mã không tuân thủ, cũng không phải là viết nhanh hơn, cũng không phải là hiệu suất tốt hơn. Dần dần nó cũng sẽ trở nên khó viết hơn, vì bạn sẽ phải đối mặt với "sự không phù hợp trở kháng" ngày càng tăng với những gì mã tuân thủ cung cấp và mong đợi.

... nếu tôi reintrepret_cast? Hoặc làm số học con trỏ phức tạp? Hay những vụ hack như vậy? "

Thật vậy, nếu bạn tập trung vào nó, bạn có thể viết mã làm rối tung mọi thứ mặc dù chơi tốt với các hướng dẫn. Nhưng:

  1. Bạn sẽ làm điều này hiếm khi (về các vị trí trong mã, không nhất thiết là về tỷ lệ thời gian thực hiện)
  2. Bạn sẽ chỉ làm điều này có chủ ý, không vô tình.
  3. Làm như vậy sẽ nổi bật trong một cơ sở mã theo hướng dẫn.
  4. Đây là loại mã mà bạn sẽ bỏ qua GC bằng ngôn ngữ khác.

... phát triển thư viện? "

Nếu bạn là nhà phát triển thư viện C ++ thì bạn viết mã không an toàn liên quan đến con trỏ thô và bạn được yêu cầu mã cẩn thận và có trách nhiệm - nhưng đây là những đoạn mã tự viết được viết bởi các chuyên gia (và quan trọng hơn là được các chuyên gia xem xét).


Vì vậy, nó giống như Bjarne đã nói: Thực sự không có động lực để thu gom rác, như tất cả các bạn nhưng đảm bảo không tạo ra rác. GC đang trở thành một vấn đề với C ++.

Điều đó không có nghĩa là GC không phải là một vấn đề thú vị đối với một số ứng dụng cụ thể, khi bạn muốn sử dụng các chiến lược phân bổ và phân bổ tùy chỉnh. Đối với những người bạn muốn phân bổ tùy chỉnh và phân bổ lại, không phải là một cấp độ ngôn ngữ.


Chà, nó (cần có GC) nếu bạn đang nghiền các chuỗi .. Hãy tưởng tượng bạn có các mảng chuỗi lớn (nghĩ hàng trăm megabyte) mà bạn đang xây dựng từng phần, sau đó xử lý và xây dựng lại thành các độ dài khác nhau, xóa các chuỗi không sử dụng, kết hợp các chuỗi khác, v.v. biết vì tôi đã phải chuyển sang ngôn ngữ cấp cao để đối phó. (Tất nhiên bạn cũng có thể xây dựng GC của riêng mình).
www-0av-Com

2
@ user1863152: Đó là trường hợp phân bổ tùy chỉnh sẽ hữu ích. Nó vẫn không cần phải có một ngôn ngữ tích hợp ngôn ngữ ...
einpoklum

để einpoklum: đúng. Nó chỉ là con ngựa cho các khóa học. Yêu cầu của tôi là xử lý thay đổi linh hoạt các thông tin vận tải hành khách. Chủ đề hấp dẫn .. Thực sự đi xuống triết lý phần mềm.
www-0av-Com

GC như thế giới Java và .NET đã phát hiện ra cuối cùng có một vấn đề lớn - nó không mở rộng được. Khi bạn có hàng tỷ đối tượng sống trong bộ nhớ như chúng ta làm trong những ngày này với bất kỳ phần mềm không tầm thường nào, bạn sẽ phải bắt đầu viết mã để ẩn mọi thứ khỏi GC. Đó là một gánh nặng để có GC trong Java và .NET.
Zach Saw

10

Ý tưởng đằng sau C ++ là bạn sẽ không phải trả bất kỳ tác động hiệu suất nào cho các tính năng mà bạn không sử dụng. Vì vậy, việc thêm bộ sưu tập rác có nghĩa là có một số chương trình chạy thẳng trên phần cứng theo cách của C và một số trong một số loại máy ảo thời gian chạy.

Không có gì ngăn cản bạn sử dụng một số dạng con trỏ thông minh bị ràng buộc với một số cơ chế thu gom rác của bên thứ ba. Tôi dường như nhớ lại Microsoft đã làm điều gì đó tương tự với COM và nó đã không hoạt động tốt.


2
Tôi không nghĩ rằng GC yêu cầu VM. Trình biên dịch có thể thêm mã vào tất cả các hoạt động con trỏ để cập nhật trạng thái toàn cục, trong khi một luồng riêng biệt chạy trong nền xóa các đối tượng khi cần.
user83255

3
Tôi đồng ý. Bạn không cần một máy ảo, nhưng lần thứ hai bạn bắt đầu có thứ gì đó quản lý bộ nhớ cho bạn như thế trong nền, tôi cảm thấy rằng bạn đã rời khỏi "dây điện" thực sự và có một tình huống VM.
Uri


4

Một trong những nguyên tắc cơ bản đằng sau ngôn ngữ C ban đầu là bộ nhớ được tạo thành từ một chuỗi byte và mã chỉ cần quan tâm đến ý nghĩa của các byte đó tại thời điểm chính xác mà chúng đang được sử dụng. Modern C cho phép trình biên dịch áp đặt các hạn chế bổ sung, nhưng C bao gồm - và C ++ giữ lại - khả năng phân tách một con trỏ thành một chuỗi byte, tập hợp bất kỳ chuỗi byte nào chứa cùng giá trị vào một con trỏ, sau đó sử dụng con trỏ đó để truy cập các đối tượng trước đó.

Mặc dù khả năng đó có thể hữu ích - hoặc thậm chí không thể thiếu - trong một số loại ứng dụng, một ngôn ngữ bao gồm khả năng đó sẽ rất hạn chế trong khả năng hỗ trợ bất kỳ loại bộ sưu tập rác đáng tin cậy và hữu ích nào. Nếu một trình biên dịch không biết mọi thứ đã được thực hiện với các bit tạo ra một con trỏ, thì nó sẽ không có cách nào để biết liệu thông tin có đủ để tái tạo lại con trỏ có thể tồn tại ở đâu đó trong vũ trụ hay không. Vì thông tin đó có thể được lưu trữ theo cách mà máy tính sẽ không thể truy cập ngay cả khi nó biết về chúng (ví dụ: các byte tạo nên con trỏ có thể được hiển thị trên màn hình đủ lâu để ai đó viết chúng nằm trên một tờ giấy), máy tính có thể không thể biết được liệu con trỏ có thể được sử dụng trong tương lai hay không.

Một điều thú vị của nhiều khung công tác thu gom rác là tham chiếu đối tượng không được xác định bởi các mẫu bit có trong đó, mà bởi mối quan hệ giữa các bit được giữ trong tham chiếu đối tượng và thông tin khác được lưu giữ ở nơi khác. Trong C và C ++, nếu mẫu bit được lưu trữ trong một con trỏ xác định một đối tượng, mẫu bit đó sẽ xác định đối tượng đó cho đến khi đối tượng bị phá hủy rõ ràng. Trong một hệ thống GC điển hình, một đối tượng có thể được biểu thị bằng mẫu bit 0x1234ABCD tại một thời điểm, nhưng chu trình GC tiếp theo có thể thay thế tất cả các tham chiếu đến 0x1234ABCD bằng các tham chiếu đến 0x4321BABE, trong đó đối tượng sẽ được biểu thị bằng mẫu sau. Ngay cả khi người ta hiển thị mẫu bit được liên kết với tham chiếu đối tượng và sau đó đọc lại từ bàn phím,


Đó là một điểm thực sự tốt, gần đây tôi đã đánh cắp một số bit từ con trỏ của mình bởi vì nếu không sẽ có những lượng bộ nhớ cache ngu ngốc.
qua đường vào

@PasserBy: Tôi tự hỏi có bao nhiêu ứng dụng sử dụng con trỏ 64 bit sẽ có lợi hơn từ việc sử dụng con trỏ 32 bit được chia tỷ lệ làm tham chiếu đối tượng hoặc cách khác giữ hầu hết mọi thứ trong 4GiB của không gian địa chỉ và sử dụng các đối tượng đặc biệt để lưu trữ / truy xuất dữ liệu từ mức cao lưu trữ tốc độ vượt quá? Các máy có đủ RAM mà mức tiêu thụ RAM của con trỏ 64 bit có thể không quan trọng, ngoại trừ việc chúng ngấu nghiến bộ nhớ cache gấp đôi so với con trỏ 32 bit.
supercat

3

Tất cả các cuộc nói chuyện kỹ thuật là quá phức tạp các khái niệm.

Nếu bạn tự động đặt GC vào C ++ cho tất cả bộ nhớ thì hãy xem xét một cái gì đó giống như trình duyệt web. Trình duyệt web phải tải một tài liệu web đầy đủ VÀ chạy các tập lệnh web. Bạn có thể lưu trữ các biến tập lệnh web trong cây tài liệu. Trong một tài liệu LỚN trong một trình duyệt có nhiều tab được mở, điều đó có nghĩa là mỗi khi GC phải thực hiện một bộ sưu tập đầy đủ, nó cũng phải quét tất cả các thành phần tài liệu.

Trên hầu hết các máy tính, điều này có nghĩa là FAULTS PAGE sẽ xảy ra. Vì vậy, lý do chính, để trả lời câu hỏi là FAULTS TRANG sẽ xảy ra. Bạn sẽ biết điều này như khi PC của bạn bắt đầu thực hiện nhiều truy cập đĩa. Điều này là do GC phải chạm vào nhiều bộ nhớ để chứng minh các con trỏ không hợp lệ. Khi bạn có một ứng dụng thực sự sử dụng nhiều bộ nhớ, việc phải quét tất cả các đối tượng, mọi bộ sưu tập đều bị tàn phá vì các CÔNG CỤ TRANG. Một lỗi trang là khi bộ nhớ ảo cần được đọc lại vào RAM từ đĩa.

Vì vậy, giải pháp chính xác là chia một ứng dụng thành các phần cần GC và các phần không. Trong trường hợp ví dụ về trình duyệt web ở trên, nếu cây tài liệu được phân bổ bằng malloc, nhưng javascript chạy với GC, thì mỗi khi GC khởi động nó chỉ quét một phần nhỏ bộ nhớ và tất cả các phần tử PAGED OUT của bộ nhớ cho cây tài liệu không cần phải phân trang lại.

Để hiểu rõ hơn về vấn đề này, hãy tìm kiếm trên bộ nhớ ảo và cách nó được thực hiện trong máy tính. Đó là tất cả về thực tế là 2GB có sẵn cho chương trình khi không có nhiều RAM. Trên các máy tính hiện đại có RAM 2GB cho hệ thống 32BIt, đây không phải là vấn đề như vậy chỉ có một chương trình đang chạy.

Như một ví dụ bổ sung, hãy xem xét một bộ sưu tập đầy đủ phải theo dõi tất cả các đối tượng. Trước tiên, bạn phải quét tất cả các đối tượng có thể truy cập thông qua rễ. Lần thứ hai quét tất cả các đối tượng có thể nhìn thấy trong bước 1. Sau đó quét các hàm hủy chờ. Sau đó đi đến tất cả các trang một lần nữa và tắt tất cả các đối tượng vô hình. Điều này có nghĩa là nhiều trang có thể bị tráo đổi và quay lại nhiều lần.

Vì vậy, câu trả lời của tôi muốn nói ngắn gọn là số lượng FAST PAGE xảy ra do chạm vào tất cả bộ nhớ gây ra toàn bộ GC cho tất cả các đối tượng trong một chương trình là không khả thi và vì vậy, lập trình viên phải xem GC như một sự trợ giúp cho những thứ như tập lệnh và cơ sở dữ liệu làm việc, nhưng làm những việc bình thường với quản lý bộ nhớ thủ công.

Và lý do rất quan trọng khác của khóa học là các biến toàn cầu. Để người sưu tầm biết rằng một con trỏ biến toàn cục nằm trong GC, nó sẽ yêu cầu các từ khóa cụ thể và do đó mã C ++ hiện tại sẽ không hoạt động.


3

RÚT GỌN TRẢ LỜI: Chúng tôi không biết cách thu gom rác hiệu quả (với thời gian nhỏ và không gian trên không) và chính xác mọi lúc (trong mọi trường hợp có thể).

TRẢ LỜI DÀI: Giống như C, C ++ là ngôn ngữ hệ thống; điều này có nghĩa là nó được sử dụng khi bạn đang viết mã hệ thống, ví dụ: hệ điều hành. Nói cách khác, C ++ được thiết kế, giống như C, với hiệu suất tốt nhất có thể là mục tiêu chính. Tiêu chuẩn của ngôn ngữ sẽ không thêm bất kỳ tính năng nào có thể cản trở mục tiêu hiệu suất.

Điều này tạm dừng câu hỏi: Tại sao bộ sưu tập rác cản trở hiệu suất? Lý do chính là, khi thực hiện, chúng tôi [các nhà khoa học máy tính] không biết cách thu gom rác với chi phí tối thiểu, trong mọi trường hợp. Do đó, trình biên dịch và hệ thống thời gian chạy C ++ không thể thực hiện thu gom rác hiệu quả mọi lúc. Mặt khác, một lập trình viên C ++, nên biết thiết kế / triển khai của anh ấy và anh ấy là người tốt nhất để quyết định cách tốt nhất để thu gom rác.

Cuối cùng, nếu điều khiển (phần cứng, chi tiết, v.v.) và hiệu suất (thời gian, không gian, sức mạnh, v.v.) không phải là những hạn chế chính, thì C ++ không phải là công cụ ghi. Ngôn ngữ khác có thể phục vụ tốt hơn và cung cấp quản lý thời gian chạy [ẩn] nhiều hơn, với chi phí cần thiết.


3

Khi chúng ta so sánh C ++ với Java, chúng ta thấy rằng C ++ không được thiết kế với Bộ sưu tập Rác ngầm trong khi Java là.

Có những thứ như con trỏ tùy ý trong C-Style không chỉ xấu cho việc triển khai GC, mà còn phá hủy khả năng tương thích ngược với một lượng lớn C ++ - mã kế thừa.

Thêm vào đó, C ++ là một ngôn ngữ được dự định để chạy dưới dạng thực thi độc lập thay vì có một môi trường thời gian chạy phức tạp.

Tất cả trong tất cả: Có, có thể thêm Bộ sưu tập rác vào C ++, nhưng vì tính liên tục, tốt hơn là không làm như vậy.


1
Giải phóng bộ nhớ và các bộ hủy chạy là vấn đề quá tách biệt hoàn toàn. (Java không có hàm hủy, là PITA.) GC giải phóng bộ nhớ, nó không chạy các bộ đệm.
tò mò

0

Chủ yếu vì hai lý do:

  1. Bởi vì nó không cần một (IMHO)
  2. Bởi vì nó không tương thích nhiều với RAII, vốn là nền tảng của C ++

C ++ đã cung cấp quản lý bộ nhớ thủ công, phân bổ ngăn xếp, RAII, container, con trỏ tự động, con trỏ thông minh ... Thế là đủ. Công cụ thu gom rác dành cho những lập trình viên lười biếng, những người không muốn dành 5 phút để suy nghĩ xem ai nên sở hữu đối tượng nào hoặc khi nào nên giải phóng tài nguyên. Đó không phải là cách chúng tôi làm mọi thứ trong C ++.


Có rất nhiều thuật toán (mới hơn) vốn khó thực hiện nếu không có bộ sưu tập rác. Thời gian trôi đi. Sự đổi mới cũng đến từ những hiểu biết mới phù hợp với ngôn ngữ cấp cao (thu gom rác). Hãy thử backport bất kỳ trong số này để C ++ miễn phí, bạn sẽ nhận thấy những va chạm trên đường. (Tôi biết tôi nên đưa ra ví dụ, nhưng hiện tại tôi đang rất vội. Xin lỗi. Tôi có thể nghĩ ngay bây giờ xoay quanh các cấu trúc dữ liệu liên tục, trong đó tính năng tham chiếu sẽ không hoạt động.).
BitTickler

0

Áp dụng thu gom rác thực sự là một sự thay đổi mô hình từ cấp thấp đến cấp cao.

Nếu bạn nhìn vào cách xử lý chuỗi trong một ngôn ngữ với bộ sưu tập rác, bạn sẽ thấy chúng CHỈ cho phép các hàm thao tác chuỗi mức cao và không cho phép truy cập nhị phân vào chuỗi. Nói một cách đơn giản, trước tiên tất cả các hàm chuỗi kiểm tra các con trỏ để xem chuỗi đó ở đâu, ngay cả khi bạn chỉ vẽ ra một byte. Vì vậy, nếu bạn đang thực hiện một vòng lặp xử lý từng byte trong một chuỗi bằng ngôn ngữ với bộ sưu tập rác, thì nó phải tính toán vị trí cơ sở cộng với bù cho mỗi lần lặp, bởi vì nó không thể biết khi nào chuỗi đã di chuyển. Sau đó, bạn phải suy nghĩ về đống, ngăn xếp, chủ đề, vv

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.