Trong C ++, bao nhiêu thời gian lập trình viên dành cho việc quản lý bộ nhớ


39

Những người đã quen với các ngôn ngữ được thu thập rác thường sợ quản lý bộ nhớ của C ++. Có các công cụ, thích auto_ptrshared_ptrsẽ xử lý nhiều tác vụ quản lý bộ nhớ cho bạn. Rất nhiều thư viện C ++ có trước các công cụ đó và có cách riêng để xử lý các tác vụ quản lý bộ nhớ.

Bạn dành bao nhiêu thời gian cho các nhiệm vụ quản lý bộ nhớ?

Tôi nghi ngờ rằng nó phụ thuộc rất nhiều vào tập hợp các thư viện bạn sử dụng, vì vậy vui lòng cho biết câu trả lời của bạn áp dụng cho câu hỏi nào, và nếu chúng làm cho nó tốt hơn hay tệ hơn.


1
Không nhiều lắm, thực sự ... Đặc biệt là với C ++ 0x, tài liệu tham khảo và STL. Bạn thậm chí có thể viết mã mà không cần quản lý bộ nhớ.
Coder

9
Nói chung: Không nhiều nếu bạn có kinh nghiệm. Rất nhiều nếu bạn chưa quen với C ++ (-> thường săn lùng rò rỉ bộ nhớ / tài nguyên).
MaR

1
Tôi tìm thấy câu hỏi thực sự, những ngày này, nhiều hơn về việc theo đuổi các tài liệu tham khảo cũ. Và nó thường khá rõ ràng mỗi lần, thật khó chịu khi nó không bị bắt trước đó: p
Matthieu M.

Tôi biết điều này đã cũ, nhưng quản lý bộ nhớ IMO là một phần không thể thiếu để trở thành một lập trình viên giỏi. Trừu tượng như các thùng chứa STL là tốt, nhưng sự thiếu hiểu biết về bộ nhớ là trái với chính ý tưởng tính toán. Người ta cũng có thể hỏi làm thế nào người ta có thể loại bỏ thao tác đại số, logic và lặp từ kho vũ khí của lập trình viên.
imallett

Làm thế nào về "bao nhiêu thời gian được sử dụng để gỡ lỗi quản lý bộ nhớ đã đi lạc?" Theo tôi, quản lý bộ nhớ là có thể và không quá khó trong C ++. Thực tế là: thiết lập nó là một nghề chính xác, và nó rất dễ bị điên. Khi bạn phát điên, bạn thậm chí có thể không nhận thấy và theo dõi lại các lỗi cũ với các hành vi thất thường chồng chất theo thời gian, là thời gian thực mà bạn nên sợ. Đó là lý do tại sao các ngôn ngữ không được thu gom rác hiện đại, (tôi nghĩ là rỉ sét) đã chuyển rất nhiều trách nhiệm trong việc kiểm tra các lỗi điển hình đối với trình biên dịch.
ZJR

Câu trả lời:


54

Modern C ++ khiến bạn không phải lo lắng về việc quản lý bộ nhớ cho đến khi bạn phải làm điều đó, cho đến khi bạn cần tổ chức bộ nhớ của mình bằng tay, chủ yếu là cho mục đích tối ưu hóa, hoặc nếu bối cảnh buộc bạn phải làm điều đó (nghĩ rằng phần cứng hạn chế lớn). Tôi đã viết toàn bộ trò chơi mà không cần thao tác bộ nhớ thô, chỉ lo lắng về việc sử dụng các thùng chứa là công cụ phù hợp cho công việc, như trong bất kỳ ngôn ngữ nào.

Vì vậy, nó phụ thuộc vào dự án, nhưng phần lớn thời gian không phải là quản lý bộ nhớ mà bạn phải xử lý mà chỉ là đối tượng suốt đời. Điều đó được giải quyết bằng cách sử dụng con trỏ thông minh , đó là một trong những công cụ C ++ thành ngữ do RAII .

Khi bạn hiểu RAII , quản lý bộ nhớ sẽ không thành vấn đề.

Sau đó, khi bạn cần truy cập vào bộ nhớ thô, bạn sẽ thực hiện theo mã rất cụ thể, cục bộ và có thể nhận dạng, như trong triển khai đối tượng nhóm, chứ không phải "ở mọi nơi".

Ngoài loại mã này, bạn sẽ không cần thao tác bộ nhớ, chỉ các đối tượng trọn đời.

Phần "khó" là để hiểu RAII.


10
Hoàn toàn đúng. Trong 5 năm qua, tôi chỉ viết "xóa" khi làm việc với mã kế thừa.
drxzcl

3
Tôi làm việc trong một môi trường nhúng bị ràng buộc kích thước ngăn xếp rất cao. Tuyệt vời như RAII, nó không hoạt động tốt nếu không gian ngăn xếp ở mức cao. Vì vậy, nó trở lại con trỏ quản lý vi mô.
bastibe

1
@nikie Tôi sử dụng các thư viện con trỏ thông minh trong mã thao tác API của họ, sau đó tôi sử dụng các con trỏ thông minh tiêu chuẩn hoặc mã hóa cụ thể cho ứng dụng của mình (nếu tôi là người quyết định về điều đó). Nếu bạn có thể cô lập mã thư viện trong một số mô-đun trừu tượng cách chúng được sử dụng trong ứng dụng của bạn, thì bạn sẽ tránh được ô nhiễm API từ các phụ thuộc.
Klaim

12
@Paperflyer: RAII sẽ không chiếm nhiều dung lượng ngăn xếp hơn deletethủ công, trừ khi bạn có một triển khai shitty.
DeadMG

2
@Paperflyer: Con trỏ thông minh trên heap chiếm cùng một không gian; sự khác biệt là trình biên dịch chèn mã giải quyết tài nguyên trên tất cả các lần thoát khỏi một hàm. Và vì nó được sử dụng rộng rãi, nên nó thường được tối ưu hóa tốt (ví dụ: gấp nhiều lối thoát lại với nhau theo cách mà bạn không thể - bạn không thể đặt mã sau một return)
MSalters

32

Quản lý bộ nhớ được sử dụng để dọa trẻ em, nhưng đó chỉ là một loại tài nguyên mà một lập trình viên phải chăm sóc. Hãy nghĩ rằng xử lý tệp, kết nối mạng, các tài nguyên khác mà bạn có được từ HĐH.

Các ngôn ngữ hỗ trợ thu gom rác thường không chỉ bỏ qua sự tồn tại của các tài nguyên này mà còn khiến việc xử lý chúng trở nên khó khăn hơn bằng cách không cung cấp hàm hủy.

Vì vậy, trong ngắn hạn, tôi đề nghị rằng không có nhiều thời gian của nhà phát triển C ++ dành cho việc lo lắng về quản lý bộ nhớ. Như câu trả lời của klaim chỉ ra, một khi bạn nắm được RAII, phần còn lại chỉ là phản xạ.


3
Tôi đặc biệt yêu thích cách httpWebRequest.GetResponse xử lý rò rỉ và bắt đầu gặp sự cố trong các ngôn ngữ GC. GC là mát mẻ, cho đến khi nó bắt đầu hút vì tài nguyên vẫn bị rò rỉ. msdn.microsoft.com/en-us/l Library / từ Xem "Chú ý".
Coder

6
+1 để xem bộ nhớ dưới dạng tài nguyên. Mã kế thừa hay không, chúng ta cần phải hét to bao nhiêu lần: Quản lý bộ nhớ là một kỹ năng chứ không phải là một lời nguyền .
thủy thủ

4
@Coder Không chắc chắn nếu tôi theo dõi .. GC hút bởi vì dù sao cũng có thể lạm dụng tài nguyên ..? Tôi nghĩ rằng C # thực hiện tốt công việc cung cấp tài nguyên xác định bằng cách sử dụng IDis Dùng ...
Tối đa

8
@Max: Bởi vì nếu đó là rác được thu thập, thì tôi hy vọng không phải lo lắng về tài nguyên ngu ngốc thông qua việc sử dụng và IDisposeables tùy chỉnh. Tài nguyên rời khỏi phạm vi, đó là nó, chúng nên được làm sạch. Tuy nhiên, trong thực tế, tôi vẫn phải suy nghĩ và đoán xem cái nào sẽ bị rò rỉ, và cái nào sẽ không. Đánh bại bất kỳ lý do để sử dụng ngôn ngữ GC ở nơi đầu tiên.
Coder

5
@deadalnix Họ có finalizecấu trúc. Tuy nhiên, bạn không biết khi nào nó sẽ được gọi. Nó sẽ là trước khi bạn hết ổ cắm, hoặc các đối tượng WebResponse? Bạn sẽ tìm thấy nhiều bài báo cho bạn biết rằng bạn không nên dựa vào finalize- với lý do chính đáng.
Dysaster

13

Khá nhiều không có. Ngay cả các công nghệ cũ như COM, bạn có thể viết các thông số tùy chỉnh cho các con trỏ tiêu chuẩn sẽ chuyển đổi chúng trong một thời gian rất ngắn. Ví dụ: std::unique_ptrcó thể được chuyển đổi thành giữ một tham chiếu COM duy nhất với năm dòng của một deleter tùy chỉnh. Ngay cả khi bạn phải tự viết trình xử lý tài nguyên của riêng mình, sự phổ biến của kiến ​​thức như SRP và sao chép và trao đổi giúp việc viết một lớp quản lý tài nguyên để sử dụng mãi mãi là điều tương đối dễ dàng.

Thực tế là tất cả đều được chia sẻ, duy nhất và không sở hữu với trình biên dịch C ++ 11 của bạn và bạn chỉ cần viết các bộ điều hợp nhỏ để làm cho chúng hoạt động ngay cả với mã cũ.


1
Bạn phải có bao nhiêu kỹ năng với C ++ để a) viết một trình xóa tùy chỉnh b) biết rằng một trình phân tích tùy chỉnh là thứ bạn cần? Tôi hỏi bởi vì có vẻ dễ dàng để chọn một ngôn ngữ GC'd mới và gần đúng mà không cần biết toàn bộ - có dễ dàng để có được ngay trong C ++ không?
Sean McMillan

1
@SeanMcMillan: Các thông số tùy chỉnh rất đơn giản để viết và triển khai, COM mà tôi đã đề cập là năm dòng cho tất cả các loại COM và bất kỳ ai có đào tạo cơ bản về C ++ hiện đại đều phải quen thuộc với chúng. Bạn không thể chọn ngôn ngữ được phân loại bởi vì bất ngờ - GC sẽ không thu thập các đối tượng COM. Hoặc xử lý tập tin. Hoặc bộ nhớ thu được từ các hệ thống khác. Hoặc kết nối cơ sở dữ liệu. RAII sẽ làm tất cả những điều đó.
DeadMG

2
Bằng cách "Chọn ngôn ngữ của GC", tôi có nghĩa là tôi đã nhảy giữa Java / C # / Ruby / Perl / Javascript / Python và tất cả chúng đều có cùng một kiểu quản lý tài nguyên - bộ nhớ chủ yếu là tự động và mọi thứ khác , bạn phải quản lý. Tôi nghe có vẻ như bạn đang nói rằng các công cụ quản lý của C ++ cho phép bạn quản lý các xử lý tệp / kết nối db / vv giống như bộ nhớ và điều đó tương đối đơn giản khi bạn tìm hiểu. Không phẫu thuật não. Tôi có hiểu đúng?
Sean McMillan

3
@SeanMcMillan: Vâng, điều đó hoàn toàn chính xác và nó không phức tạp.
DeadMG

11

Khi tôi là một lập trình viên C ++ (một thời gian dài trước đây), tôi đã mất một thời gian dài lo lắng về lỗi quản lý bộ nhớ khi cố gắng khắc phục khó khăn để tái tạo lỗi .

Với modem C ++, việc quản lý bộ nhớ ít gặp vấn đề hơn, nhưng bạn có thể tin tưởng tất cả mọi người trong một nhóm lớn để làm cho đúng. Chi phí / thời gian của:

  • Đào tạo (không có nhiều lập trình viên phát triển với sự hiểu biết tốt về các vấn đề)
  • Mã đánh giá để tìm các vấn đề quản lý bộ nhớ
  • Gỡ lỗi các vấn đề quản lý bộ nhớ
  • Luôn phải nhớ rằng một lỗi trong một phần của ứng dụng, có thể là do vấn đề quản lý bộ nhớ trong một phần không liên quan của ứng dụng .

Vì vậy, đây không chỉ là thời gian dành cho việc thực hiện của Google , đây là vấn đề của các dự án lớn.


2
Tôi nghĩ rằng một số dự án C ++ đã tuyệt vọng về việc sửa một số rò rỉ bộ nhớ của họ do mã được viết xấu. Mã xấu sẽ xảy ra, và khi nó xảy ra, nó cũng có thể chiếm rất nhiều thời gian của người khác.
Jeremy

@Jeremy, tôi đã tìm thấy khi tôi chuyển từ C ++ sang C #, vẫn còn nhiều mã được viết xấu (nếu không phải là nhiều hơn), nhưng ít nhất rất dễ tìm thấy phần của chương trình có lỗi đã cho.
Ian

1
vâng, đây là lý do tại sao hầu hết các cửa hàng đã chuyển sang Java hoặc .NET. Thu gom rác giảm thiểu thiệt hại không thể tránh khỏi của mã xấu.
Jeremy

1
Thật kỳ lạ, chúng ta không có những vấn đề đó.
David Thornley

1
@DavidThornley, tôi nghĩ rằng rất nhiều vấn đề là do viết mã UI trong C ++, ngày nay hầu hết mã C ++ tôi thấy không phải là UI
Ian

2

Tôi sử dụng các thư viện boost và TR1 rất nhiều và chúng làm cho việc quản lý bộ nhớ theo nghĩa chặt chẽ (mới / xóa) không thành vấn đề. Mặt khác, phân bổ bộ nhớ trong C ++ không rẻ và người ta phải chú ý đến nơi những con trỏ chia sẻ ưa thích này được tạo ra. Cuối cùng, bạn sử dụng nhiều không gian làm việc hoặc làm việc với bộ nhớ dựa trên ngăn xếp. Nói chung, tôi muốn nói rằng đó chủ yếu là vấn đề thiết kế, không phải là vấn đề triển khai.


2

mất bao nhiêu thời gian của một khách hàng? rất ít, một khi bạn có được hang của nó. khi một container quản lý trọn đời và các tài liệu tham khảo, nó thực sự rất dễ dàng. imo, nó đơn giản hơn nhiều so với đếm tham chiếu thủ công và thực tế sẽ minh bạch nếu bạn xem xét container bạn sử dụng làm tài liệu mà trình biên dịch thuận tiện ngăn bạn thực hiện chuyển quyền sở hữu không hợp lệ trong một hệ thống an toàn được thiết kế tốt.

hầu hết thời gian tôi dành (với tư cách là khách hàng) được dành để chứa các loại từ các apis khác, vì vậy chúng hoạt động tốt trong bối cảnh các chương trình của bạn. Ví dụ: đây là thùng chứa ThirdPartyFont của tôi, và nó hỗ trợ các tính năng này, và dụng cụ phá hủy theo cách này, và tài liệu tham khảo tính cách này, và sao chép theo cách này, và ... . Nhiều trong số các cấu trúc đó cần được đặt đúng chỗ, và nó thường là nơi hợp lý để đặt chúng. dù bạn muốn bao gồm điều đó theo thời gian hay không phụ thuộc vào định nghĩa của bạn (việc triển khai cần tồn tại khi giao tiếp với các apis này, dù sao, phải không?).

sau đó, bạn sẽ cần cân nhắc bộ nhớ và quyền sở hữu. trong một hệ thống cấp thấp hơn, điều đó tốt và cần thiết, nhưng có thể mất một chút thời gian và giàn giáo để thực hiện cách bạn nên di chuyển mọi thứ xung quanh. Tôi không thấy đó là một nỗi đau vì đây là yêu cầu của hệ thống cấp thấp hơn. quyền sở hữu, kiểm soát và trách nhiệm là hiển nhiên.

vì vậy chúng ta có thể chuyển hướng sang apis dựa trên c sử dụng các loại mờ: các thùng chứa của chúng ta cho phép chúng ta trừu tượng hóa tất cả các chi tiết thực hiện nhỏ trong việc quản lý vòng đời và sao chép các loại mờ đó, điều này làm cho việc quản lý tài nguyên rất đơn giản và tiết kiệm thời gian, khiếm khuyết, và giảm việc thực hiện.

sử dụng những thứ này thực sự rất đơn giản - vấn đề (đến từ GC) là bây giờ bạn phải xem xét đến thời gian sống của tài nguyên của mình. nếu bạn hiểu sai, có thể mất rất nhiều thời gian để giải quyết. học tập và tích hợp quản lý trọn đời rõ ràng là phức tạp dễ hiểu khi so sánh (không dành cho tất cả mọi người) - đó là rào cản thực sự. một khi bạn cảm thấy thoải mái khi kiểm soát tuổi thọ và sử dụng các giải pháp tốt, thì việc quản lý tuổi thọ tài nguyên thực sự rất dễ dàng. đó không phải là một phần quan trọng trong ngày của tôi (trừ khi một lỗi khó đã xuất hiện).

nếu bạn không sử dụng các thùng chứa (con trỏ tự động / dùng chung), thì bạn chỉ đang đau đớn.

Tôi đã thực hiện các thư viện của riêng mình. tôi phải mất thời gian để thực hiện những điều đó, nhưng hầu hết mọi người đều sử dụng lại (thường là một ý tưởng tốt).


1

Bạn có nghĩa là như phải tự giải phóng bộ nhớ, đóng tệp, những thứ thuộc loại này? Nếu vậy, tôi sẽ nói mức tối thiểu và thường ít hơn hầu hết các ngôn ngữ khác mà tôi đã sử dụng, đặc biệt là nếu chúng tôi khái quát hóa không chỉ là "quản lý bộ nhớ" mà còn "quản lý tài nguyên". Theo nghĩa đó, tôi thực sự nghĩ rằng C ++ yêu cầu quản lý tài nguyên thủ công ít hơn so với Java hoặc C #.

Điều này chủ yếu là do các công cụ phá hủy tự động phá hủy tài nguyên (bộ nhớ hoặc cách khác). Thông thường, lần duy nhất tôi phải giải phóng / hủy tài nguyên theo cách thủ công trong C ++ là nếu tôi đang triển khai cấu trúc dữ liệu ở mức độ thấp (điều mà hầu hết mọi người không cần phải làm) hoặc sử dụng API C khi tôi chỉ mất một ít thời gian gói tài nguyên C cần được giải phóng / hủy / đóng thủ công vào trình bao bọc C ++ tuân thủ RAII.

Tất nhiên, nếu người dùng yêu cầu đóng hình ảnh trong phần mềm chỉnh sửa ảnh, tôi phải xóa hình ảnh khỏi bộ sưu tập hoặc thứ gì đó. Nhưng hy vọng rằng điều đó không được tính là quản lý "bộ nhớ" hoặc "tài nguyên" thuộc loại quan trọng trong bối cảnh này, vì điều đó được yêu cầu khá nhiều trong bất kỳ ngôn ngữ nào nếu bạn muốn giải phóng bộ nhớ liên quan đến hình ảnh đó vào thời điểm đó. Nhưng một lần nữa, tất cả những gì bạn phải làm là xóa hình ảnh khỏi bộ sưu tập và bộ hủy hình ảnh sẽ đảm nhiệm phần còn lại.

Trong khi đó, nếu tôi so sánh với Java hoặc C #, bạn thường thấy mọi người phải đóng các tệp theo cách thủ công ở đó, ngắt kết nối ổ cắm theo cách thủ công, đặt tham chiếu đối tượng thành null để cho phép chúng được thu gom rác, v.v. Có nhiều bộ nhớ thủ công hơn và quản lý tài nguyên trong các ngôn ngữ đó nếu bạn hỏi tôi. Trong C ++, bạn thường không cần phải sử dụng unlockmutex một cách thủ công, vì trình khóa mutex sẽ tự động làm điều đó khi mutex vượt quá phạm vi. Ví dụ, bạn không bao giờ phải làm những việc như thế này trong C ++:

System.IO.StreamReader file = new System.IO.StreamReader(path);
try
{
    file.ReadBlock(buffer, index, buffer.Length);
}
catch (System.IO.IOException e)
{
    ...
}
finally
{
    if (file != null)
        file.Close();
}

Không cần phải làm những việc như đóng tệp thủ công trong C ++. Cuối cùng, họ tự động đóng cửa ngay lập tức khi họ đi ra khỏi phạm vi cho dù họ đi ra khỏi phạm vi như là kết quả hoặc đường dẫn thực thi thông thường hoặc đặc biệt. Điều tương tự cho các tài nguyên liên quan đến bộ nhớ như std::vector. Mã như file.Close()trên thường sẽ được tán thành vì, đặc biệt là trong bối cảnh của một finallykhối, điều đó cho thấy tài nguyên cục bộ cần được giải phóng bằng tay khi toàn bộ suy nghĩ xung quanh C ++ là tự động hóa điều đó.

Về mặt quản lý bộ nhớ thủ công, tôi muốn nói C yêu cầu tối đa, Java / C # một lượng trung bình và C ++ tối thiểu trong số này. Có nhiều lý do để hơi ngại sử dụng C ++ vì đây là ngôn ngữ rất khó để thành thạo, nhưng quản lý bộ nhớ không nên là một trong số đó. Ngược lại, tôi thực sự nghĩ rằng đó là một trong những ngôn ngữ dễ nhất ngoài kia về khía cạnh này.

Tất nhiên C ++ không cho phép bạn bắt đầu phân bổ bộ nhớ theo cách thủ công và gọi operator delete/delete[]bộ nhớ trống thủ công. Nó cũng cho phép bạn sử dụng các hàm C như mallocfree. Nhưng đó là cách thực hành mã hóa theo kiểu cổ xưa mà tôi nghĩ đã trở nên lỗi thời từ lâu trước khi mọi người tín dụng, vì Stroustrup đã ủng hộ RAII trước khi anh ta đặt ra thuật ngữ từ rất sớm. Vì vậy, tôi thậm chí không nghĩ rằng thật công bằng khi nói "C ++ hiện đại" tự động hóa việc quản lý tài nguyên, bởi vì đó được cho là mục đích xuyên suốt. Bạn thực tế không thể có được sự an toàn ngoại lệ. Chỉ là rất nhiều nhà phát triển sai lầm trong những năm đầu thập niên 90 đã cố gắng sử dụng C ++ giống như C với các đối tượng, thường hoàn toàn bỏ qua việc xử lý ngoại lệ và không bao giờ được sử dụng theo cách đó. Nếu bạn sử dụng C ++ theo cách thực tế luôn được sử dụng, thì việc quản lý bộ nhớ hoàn toàn tự động và nói chung không phải là thứ bạn phải tự xử lý (hoặc nên xử lý) nhiều.


1
Java hiện đại đã "thử với các tài nguyên" để loại bỏ tất cả mã lộn xộn đó trong khối cuối cùng. Rất hiếm khi cần phải có một khối cuối cùng. Có vẻ như các nhà thiết kế đã sao chép khái niệm RAII.
kiwiron

0

Phụ thuộc vào các lãnh đạo kỹ thuật cao cấp trong đội. Trong một số công ty (bao gồm cả của tôi), không có khái niệm gọi là nhà thơ thông minh. Nó được coi là lạ mắt. Vì vậy, mọi người chỉ cần đặt xóa khắp nơi và có một ổ đĩa để sửa lỗi rò rỉ bộ nhớ trong mỗi 2 tháng. Làn sóng mới của tuyên bố xóa đến mọi nơi. Vì vậy, phụ thuộc vào công ty và loại người làm việc ở đó.


1
Có một cái gì đó trong môi trường của bạn ngăn bạn sử dụng auto_ptrvà bạn bè?
Sean McMillan

2
Nghe có vẻ như công ty của bạn không viết mã C ++, bạn đang viết C.
gbjbaanb
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.