2008-08-11 63 views
16

Vì vậy, tôi cần trợ giúp. Tôi đang làm việc trên một dự án trong C++. Tuy nhiên, tôi nghĩ rằng tôi đã bằng cách nào đó quản lý để hỏng heap của tôi. Này được dựa trên thực tế là tôi đã thêm một std::string để một lớp và gán cho nó một giá trị từ std::string khác:Quản lý bộ nhớ, Heap Corruption và C++

std::string hello = "Hello, world.\n"; 
/* exampleString = "Hello, world.\n" would work fine. */ 
exampleString = hello; 

treo trên hệ thống của tôi với một chồng đổ. Vì vậy, về cơ bản tôi cần phải dừng lại và thực hiện tất cả các công cụ quản lý mã và bộ nhớ của mình và tìm hiểu nơi tôi đã sửa chữa. Các codebase vẫn còn nhỏ (khoảng 1000 dòng), do đó, điều này là dễ dàng có thể.

Tuy nhiên, tôi vượt qua đầu của tôi với loại công cụ này, vì vậy tôi nghĩ tôi sẽ ném nó ra khỏi đó. Tôi đang trên một hệ thống Linux và đã poked xung quanh với valgrind, và trong khi không biết hoàn toàn những gì tôi đang làm, nó đã báo cáo rằng destructor của std::string là một không hợp lệ miễn phí. Tôi phải thừa nhận để nhận thuật ngữ 'Heap Corruption' từ tìm kiếm của Google; bất kỳ bài viết có mục đích chung nào về loại nội dung này cũng sẽ được đánh giá cao.

(Trong trước rm -rf ProjectDir, làm một lần nữa trong C#: D)

EDIT: tôi đã không làm cho nó rõ ràng, nhưng những gì tôi yêu cầu cho nhiều cách một lời khuyên của chẩn đoán các loại của các vấn đề bộ nhớ. Tôi biết std :: string stuff là đúng, vì vậy nó là một cái gì đó tôi đã thực hiện (hoặc một lỗi, nhưng không có vấn đề với Chọn). Tôi chắc chắn rằng tôi có thể kiểm tra mã tôi đã viết lên và bạn rất thông minh folks sẽ thấy vấn đề trong thời gian không, nhưng tôi muốn thêm loại phân tích mã vào 'hộp công cụ' của tôi, như nó được.

Trả lời

21

Đây là những cơ chế tương đối rẻ cho khả năng giải quyết vấn đề:

  1. Giữ một mắt trên của tôi heap corruption question - Tôi đang cập nhật với những câu trả lời khi họ lắc ra. Đầu tiên là cân bằng new[]delete[], nhưng bạn đã làm điều đó.
  2. Tặng thêm valgrind nhiều lần nữa; nó là một công cụ tuyệt vời, và tôi chỉ muốn nó có sẵn trong Windows. Tôi chỉ làm chậm chương trình của bạn xuống khoảng một nửa, đó là khá tốt so với Windows tương đương.
  3. Hãy nghĩ đến việc sử dụng Google Performance Tools làm điểm thay thế mới/mới.
  4. Bạn đã xóa tất cả các tệp đối tượng của mình và bắt đầu lại chưa? Có lẽ tệp tạo của bạn là ... "nhỏ hơn"
  5. Bạn không phải là assert() nhập đủ mã của bạn. Làm thế nào để tôi biết điều đó mà không thấy nó? Giống như dùng chỉ nha khoa, không có ai trong số assert() đủ trong mã của họ. Thêm vào một chức năng xác nhận cho các đối tượng của bạn và gọi đó là phương thức bắt đầu và phương thức kết thúc.
  6. Bạn có compiling -wall không? Nếu không, hãy làm như vậy.
  7. Tìm cho mình một công cụ lint như PC-Lint. Một ứng dụng nhỏ như ứng dụng của bạn có thể vừa với trang PC-lint demo, có nghĩa là không mua hàng cho bạn!
  8. Kiểm tra bạn đang trỏ trỏ NULLing sau khi xóa chúng. Không ai thích một con trỏ lơ lửng. Cùng biểu diễn với các con trỏ đã được khai báo nhưng chưa được phân bổ.
  9. Ngừng sử dụng mảng. Sử dụng vector để thay thế.
  10. Không sử dụng con trỏ thô. Sử dụng smart pointer. Không sử dụng auto_ptr! Điều đó là ... đáng ngạc nhiên; ngữ nghĩa của nó rất kỳ quặc. Thay vào đó, hãy chọn một trong số Boost smart pointers hoặc một cái gì đó từ số the Loki library.
+2

+1, danh sách tốt! Tuy nhiên, tôi sẽ tranh chấp # 8 - trong khi nó ngăn chặn các truy cập 'xấu', nó thực sự là một mùi mã ẩn logic kém hoặc quản lý lâu đời đối tượng nghèo trong kinh nghiệm của tôi ... – Roddy

+0

Ngày nay, C++ có con trỏ thông minh của riêng nó trong tiêu chuẩn thư viện, vì vậy không cần Boost hoặc Loki cho điều đó. –

0

Theo như tôi có thể nói mã của bạn là chính xác. Giả sử exampleString là một chuỗi std :: có phạm vi lớp như bạn mô tả, bạn nên có khả năng khởi tạo/gán nó theo cách đó. Có lẽ có một số vấn đề khác? Có lẽ một đoạn mã thực sự sẽ giúp đưa nó vào ngữ cảnh.

Câu hỏi: Ví dụĐưa con trỏ vào đối tượng chuỗi được tạo bằng mới?

1

Nó có thể là tham nhũng đống, nhưng nó giống như khả năng ngăn xếp tham nhũng. Jim nói đúng. Chúng tôi thực sự cần thêm một chút ngữ cảnh. Hai dòng nguồn đó không cho chúng ta biết nhiều về sự cô lập. Có thể có bất kỳ số lượng điều gây ra điều này (đó là niềm vui thực sự của C/C++).

Nếu bạn cảm thấy thoải mái khi đăng mã, bạn thậm chí có thể ném tất cả lên trên máy chủ và đăng liên kết. Tôi chắc rằng bạn sẽ nhận được nhiều lời khuyên hơn như vậy (một số của nó chắc chắn không liên quan đến câu hỏi của bạn).

1

Mã của bạn như tôi có thể thấy không có lỗi. Như đã nói nhiều bối cảnh là cần thiết.

Nếu bạn chưa thử, hãy cài đặt gdb (trình gỡ lỗi gcc) và biên dịch chương trình bằng -g. Điều này sẽ biên dịch trong các biểu tượng gỡ lỗi mà gdb có thể sử dụng. Một khi bạn đã cài đặt gdb chạy nó với chương trình (gdb). This là một cheatsheat hữu ích cho việc sử dụng gdb.

Đặt điểm ngắt cho hàm đang tạo lỗi và xem giá trị của exampleString là gì. Cũng làm tương tự cho bất kỳ tham số nào bạn đang truyền tới exampleString. Điều này ít nhất nên cho bạn biết nếu std :: strings là hợp lệ.

Tôi đã tìm thấy câu trả lời từ this article là hướng dẫn tốt về con trỏ.

1

Mã chỉ đơn giản là ví dụ về nơi chương trình của tôi không hoạt động (được phân bổ trên ngăn xếp, Jim).Tôi không thực sự tìm kiếm 'những gì tôi đã làm sai', mà là 'làm cách nào để chẩn đoán những gì tôi đã làm sai'. Dạy một người đàn ông để câu cá và tất cả những điều đó. Mặc dù nhìn vào câu hỏi, tôi đã không làm rõ điều đó. Cảm ơn lòng tốt cho chức năng chỉnh sửa. : ')

Ngoài ra, tôi thực sự đã sửa lỗi std :: string problem. Làm sao? Bằng cách thay thế nó bằng một vector, biên dịch, sau đó thay thế chuỗi một lần nữa. Nó liên tục bị rơi ở đó và điều đó được khắc phục mặc dù nó ... không thể. Có điều gì đó khó chịu ở đó, và tôi không chắc chắn điều gì. Tôi muốn kiểm tra một lần tôi tự cấp phát bộ nhớ trên heap, mặc dù:

this->map = new Area*[largestY + 1]; 
for (int i = 0; i < largestY + 1; i++) { 
    this->map[i] = new Area[largestX + 1]; 
} 

và xóa nó:

for (int i = 0; i < largestY + 1; i++) { 
    delete [] this->map[i]; 
} 
delete [] this->map; 

tôi đã không phân bổ một mảng 2ngày với C++ trước. Dường như nó hoạt động.

7

Ồ, nếu bạn muốn biết cách gỡ lỗi vấn đề, điều đó thật đơn giản. Đầu tiên, lấy một con gà chết. Sau đó, start shaking it.

Nghiêm túc, tôi đã không tìm thấy cách nhất quán để theo dõi các loại lỗi này. Bởi vì có rất nhiều vấn đề tiềm ẩn nên không có danh sách kiểm tra đơn giản nào để thực hiện. Tuy nhiên, tôi khuyên bạn nên làm như sau:

  1. Hãy thoải mái trong trình gỡ lỗi.
  2. Bắt đầu tromping xung quanh trong trình gỡ lỗi để xem bạn có thể tìm thấy bất cứ điều gì trông có vẻ cá. Kiểm tra đặc biệt để xem những gì đang xảy ra trong dòng exampleString = hello;.
  3. Kiểm tra để đảm bảo rằng nó thực sự gặp sự cố trên đường dây exampleString = hello; và không phải khi thoát khỏi một số khối bao quanh (có thể khiến cho trình phá hủy kích hoạt).
  4. Kiểm tra bất kỳ phép thuật con trỏ nào bạn có thể đang thực hiện. Số học con trỏ, truyền, v.v.
  5. Kiểm tra tất cả phân bổ và deallocations của bạn để đảm bảo chúng được khớp (không có thỏa thuận kép).
  6. Đảm bảo bạn không trả lại bất kỳ tham chiếu hoặc con trỏ nào cho các đối tượng trên ngăn xếp.

Có rất nhiều thứ khác để thử. Tôi chắc chắn một số người khác sẽ kêu vang với ý tưởng là tốt.

1

Ngoài ra, tôi thực sự đã sửa lỗi std :: string problem. Làm sao? Bằng cách thay thế nó bằng một vector, biên dịch, sau đó thay thế chuỗi một lần nữa. Nó liên tục bị rơi ở đó, và nó đã được cố định mặc dù nó ... không thể. Có điều gì đó khó chịu ở đó, và tôi không chắc chắn điều gì.

Điều đó nghe có vẻ như bạn thực sự đã lắc một con gà ở đó. Nếu bạn không biết lý do tại sao nó hoạt động ngay bây giờ, sau đó nó vẫn bị hỏng, và khá nhiều đảm bảo để cắn bạn một lần nữa sau này (sau khi bạn đã thêm phức tạp hơn nữa).

3

Một số nơi để bắt đầu:

Nếu bạn đang ở trên các cửa sổ, và sử dụng Visual C++ 6 (Tôi hy vọng sẽ không ai thần vẫn sử dụng nó những ngày này) nó implentation của std :: string không threadsafe, và có thể dẫn đến loại điều này.

Here's an article I found which explains a lot of the common causes of memory leaks and corruption.

Tại nơi làm việc trước đây của tôi, chúng tôi sử dụng Compuware Boundschecker để giúp với điều này. Đó là thương mại và rất tốn kém, vì vậy có thể không phải là một lựa chọn.

Dưới đây là một vài thư viện miễn phí mà có thể sử dụng một số

http://www.codeguru.com/cpp/misc/misc/memory/article.php/c3745/

http://www.codeproject.com/KB/cpp/MemLeakDetect.aspx

Hy vọng rằng sẽ giúp. Bộ nhớ tham nhũng là một nơi sucky để được ở!

1

Chạy Purify.

Nó là một công cụ gần như huyền diệu đó sẽ báo cáo khi bạn đang clobbering bộ nhớ bạn không nên chạm vào, rò rỉ bộ nhớ bằng cách không giải phóng những thứ, kích đúp giải phóng vv

Nó hoạt động ở cấp mã máy , vì vậy bạn thậm chí không cần phải có mã nguồn.

Một trong những cuộc gọi hội nghị nhà cung cấp thú vị nhất mà tôi từng có là khi Purify tìm thấy rò rỉ bộ nhớ trong mã của họ và chúng tôi có thể hỏi, " "và nghe thấy sự ngạc nhiên trong giọng nói của họ.

Họ nghĩ rằng chúng tôi đang gỡ lỗi các vị thần nhưng sau đó chúng tôi cho họ vào bí mật để họ có thể chạy Purify trước khi chúng tôi phải sử dụng mã của họ. :-)

http://www-306.ibm.com/software/awdtools/purify/unix/

(Nó khá đắt tiền nhưng họ có một eval tải về miễn phí)

1

Một trong những kỹ thuật gỡ lỗi mà tôi sử dụng thường xuyên (trừ những trường hợp của weirdness cực đoan nhất) là để phân chia và chinh phục. Nếu chương trình của bạn hiện không thành công với một số lỗi cụ thể, sau đó chia nó thành một nửa theo cách nào đó và xem liệu nó có còn lỗi không. Rõ ràng là lừa là quyết định nơi để chia chương trình của bạn!

Ví dụ của bạn như đã cho không hiển thị đủ ngữ cảnh để xác định vị trí của lỗi. Nếu ai khác thử ví dụ của bạn, nó sẽ hoạt động tốt. Vì vậy, trong chương trình của bạn, hãy thử xóa càng nhiều nội dung bổ sung mà bạn không hiển thị cho chúng tôi và xem liệu nó có hoạt động không. Nếu vậy, sau đó thêm mã khác vào một chút tại một thời điểm cho đến khi nó bắt đầu thất bại. Sau đó, điều bạn vừa thêm có lẽ là vấn đề.

Lưu ý rằng nếu chương trình của bạn đa luồng, thì có thể bạn có vấn đề lớn hơn. Nếu không, thì bạn sẽ có thể thu hẹp nó theo cách này. Chúc may mắn!

1

Khác với các công cụ như Boundschecker hoặc Purify, cách tốt nhất để giải quyết các vấn đề như thế này là thực sự tốt khi đọc mã và làm quen với mã mà bạn đang làm việc.

Tham nhũng bộ nhớ là một trong những điều khó khắc phục nhất và thường những loại vấn đề này được giải quyết bằng cách chi tiêu giờ/ngày trong trình gỡ lỗi và nhận thấy một cái gì đó như "hey, con trỏ X đang được sử dụng sau khi nó bị xóa!".

Nếu nó giúp ích gì, đó là điều bạn có thể làm tốt hơn khi bạn có được kinh nghiệm.

Phân bổ bộ nhớ của bạn cho mảng có vẻ chính xác, nhưng hãy đảm bảo bạn kiểm tra tất cả các vị trí mà bạn cũng truy cập vào mảng đó.

10

Chúng tôi đã từng có một lỗi ngăn cản tất cả các kỹ thuật thông thường, valgrind, làm sạch vv. Sự cố chỉ xảy ra trên các máy có nhiều bộ nhớ và chỉ trên bộ dữ liệu đầu vào lớn.

Cuối cùng, chúng tôi đã theo dõi nó bằng cách sử dụng điểm xem trình gỡ lỗi. Tôi sẽ cố gắng mô tả quy trình tại đây:

1) Tìm nguyên nhân gây ra lỗi. Nó trông từ mã ví dụ của bạn, rằng bộ nhớ cho "exampleString" đang bị hỏng, và do đó không thể được ghi vào. Hãy tiếp tục với giả định này.

2) Đặt điểm ngắt tại vị trí đã biết cuối cùng mà "exampleString" được sử dụng hoặc sửa đổi mà không gặp bất kỳ sự cố nào.

3) Thêm điểm xem vào thành viên dữ liệu của 'exampleString'. Với phiên bản g ++ của tôi, chuỗi được lưu trữ trong _M_dataplus._M_p. Chúng tôi muốn biết khi nào thành viên dữ liệu này thay đổi. Kỹ thuật GDB cho điều này là:

(gdb) p &exampleString._M_dataplus._M_p 
$3 = (char **) 0xbfccc2d8 
(gdb) watch *$3 
Hardware watchpoint 1: *$3 

Tôi đang sử dụng linux với g ++ và gdb ở đây, nhưng tôi tin rằng các điểm xem bộ nhớ khả dụng với hầu hết các trình gỡ rối.

4) Tiếp tục cho đến khi điểm chiếc đồng hồ được kích hoạt:

Continuing. 
Hardware watchpoint 2: *$3 

Old value = 0xb7ec2604 "" 
New value = 0x804a014 "" 
0xb7e70a1c in std::string::_M_mutate() from /usr/lib/libstdc++.so.6 
(gdb) where 

Lệnh gdb where sẽ cung cấp cho một dấu vết trở lại hiển thị những gì dẫn đến việc sửa đổi. Đây là một sửa đổi hoàn toàn hợp pháp, trong trường hợp này chỉ tiếp tục - hoặc nếu bạn may mắn, nó sẽ là sửa đổi do tham nhũng bộ nhớ. Trong trường hợp thứ hai, bây giờ bạn có thể xem lại mã là thực sự gây ra sự cố và hy vọng khắc phục sự cố đó.

Nguyên nhân gây ra lỗi của chúng tôi là truy cập mảng có chỉ số âm. Chỉ số này là kết quả của một diễn viên của một con trỏ đến một 'int' modulos kích thước của mảng. Các lỗi đã được bỏ qua bởi valgrind et al. vì địa chỉ bộ nhớ được cấp phát khi chạy dưới các công cụ đó không bao giờ là "> MAX_INT" và do đó không bao giờ dẫn đến chỉ mục phủ định.

+0

Thảo luận tuyệt vời cho Linux! Hoa hậu phát triển trong môi trường đó. Cần một giải pháp cho WinDoze bản thân mình ... (VS6.0 quá) ... (không phải lỗi của tôi! Khách hàng sử dụng VS6.0 & Khách hàng luôn đúng :). –