Cách theo dõi lỗi "hai lần miễn phí hoặc lỗi"


94

Khi tôi chạy chương trình (C ++) của mình, nó bị lỗi này.

* đã phát hiện glibc * ./load: miễn phí gấp đôi hoặc hỏng (! trước): 0x0000000000c6ed50 ***

Làm cách nào để tìm ra lỗi?

Tôi đã thử sử dụng câu lệnh print ( std::cout) nhưng không thành công. Có thể gdblàm cho điều này dễ dàng hơn?


5
Tôi tự hỏi tại sao mọi người đề xuất cho NULLcon trỏ (che dấu các lỗi mà nếu không, như câu hỏi này cho thấy rất độc đáo), nhưng không ai đề nghị đơn giản là không thực hiện quản lý bộ nhớ thủ công, điều này rất khả thi trong C ++. Tôi đã không viết deletetrong nhiều năm. (Và, vâng, mã của tôi rất quan trọng về hiệu suất. Nếu không, nó sẽ không được viết bằng C ++.)
sbi

2
@sbi: Tham nhũng đống và những thứ tương tự hiếm khi bị bắt, ít nhất là không xảy ra ở đâu. NULLcon trỏ ing có thể làm cho chương trình của bạn bị lỗi sớm hơn.
Hasturkun

@Hasturkun: Tôi hoàn toàn không đồng ý. Một động lực chính đối với NULLcon trỏ là ngăn chặn giây delete ptr;nổ tung - điều này đang che dấu một lỗi, bởi vì giây đó deletelẽ ra không bao giờ xảy ra. (Nó cũng được sử dụng để kiểm tra xem một con trỏ vẫn trỏ đến một đối tượng hợp lệ Nhưng đó chỉ đặt ra câu hỏi tại sao bạn có một con trỏ trong phạm vi mà không có một đối tượng để trỏ đến..)
SBI

Câu trả lời:


64

Nếu bạn đang sử dụng glibc, bạn có thể đặt MALLOC_CHECK_biến môi trường thành 2, điều này sẽ khiến glibc sử dụng phiên bản có khả năng chịu lỗi malloc, điều này sẽ khiến chương trình của bạn ngừng hoạt động tại thời điểm hoàn tất quá trình giải phóng kép.

Bạn có thể đặt điều này từ gdb bằng cách sử dụng set environment MALLOC_CHECK_ 2lệnh trước khi chạy chương trình của mình; chương trình sẽ hủy bỏ, với free()cuộc gọi hiển thị trong backtrace.

xem trang người đàn ôngmalloc() để biết thêm thông tin


2
Thiết MALLOC_CHECK_2thực sự cố định đôi vấn đề tự do của tôi (mặc dù nó không được sửa chữa nếu nó đang ở chế độ debug only)
PUK

4
@puk Tôi cũng gặp sự cố tương tự, việc đặt MALLOC_CHECK_ thành 2 không tránh được sự cố không có kép của tôi. Có những tùy chọn nào khác để đưa vào ít mã hơn để tái tạo vấn đề và cung cấp một dấu vết?
Wei Zhong

Cũng có nó để thiết lập MALLOC_CHECK_ tránh được sự cố. Các đồng nghiệp bình luận / bất kỳ ai ... bạn đã tìm ra một cách khác để trình bày vấn đề?
pellucidcoder

"Khi MALLOC_CHECK_ được đặt thành giá trị khác 0, một triển khai đặc biệt (kém hiệu quả hơn) được sử dụng được thiết kế để có thể chống lại các lỗi đơn giản, chẳng hạn như các cuộc gọi miễn phí kép với cùng một đối số hoặc vượt quá một byte đơn (tắt -bởi một lỗi). " gnu.org/software/libc/manual/html_node/… Vì vậy, có vẻ như MALLOC_CHECK_ chỉ được sử dụng để tránh các lỗi bộ nhớ đơn giản, không phát hiện ra chúng.
pellucidcoder

Trên thực tế .... support.microfocus.com/kb/doc.php?id=3113982 có vẻ như đặt MALLOC_CHECK_ thành 3 là hữu ích nhất và có thể được sử dụng để phát hiện lỗi.
pellucidcoder

32

Có ít nhất hai trường hợp có thể xảy ra:

  1. bạn đang xóa cùng một thực thể hai lần
  2. bạn đang xóa một cái gì đó không được cấp phát

Đối với cách đầu tiên, tôi thực sự khuyên bạn nên NULL-ing tất cả các con trỏ đã xóa.

Bạn có ba lựa chọn:

  1. quá tải mới và xóa và theo dõi phân bổ
  2. vâng, hãy sử dụng gdb - sau đó bạn sẽ nhận được hậu quả từ sự cố của mình và điều đó có thể sẽ rất hữu ích
  3. như được đề xuất - sử dụng Valgrind - không dễ để tham gia, nhưng nó sẽ giúp bạn tiết kiệm thời gian gấp ngàn lần trong tương lai ...

2. sẽ gây ra tham nhũng, nhưng tôi không nghĩ thông báo này nói chung sẽ xuất hiện, vì việc kiểm tra sự tỉnh táo chỉ được thực hiện trên heap. Tuy nhiên, tôi nghĩ rằng 3. có thể xảy ra tràn bộ đệm heap.
Matthew Flaschen

Tốt. Đúng, tôi đã bỏ lỡ để làm cho con trỏ NULL và phải đối mặt với lỗi này. Bài học kinh nghiệm!
hrushi

26

Bạn có thể sử dụng gdb, nhưng trước tiên tôi sẽ thử Valgrind . Xem hướng dẫn bắt đầu nhanh .

Tóm lại, Valgrind thiết lập chương trình của bạn để nó có thể phát hiện một số loại lỗi trong việc sử dụng bộ nhớ được cấp phát động, chẳng hạn như giải phóng gấp đôi và ghi qua phần cuối của khối bộ nhớ được cấp phát (có thể làm hỏng heap). Nó phát hiện và báo cáo các lỗi ngay khi chúng xảy ra , do đó chỉ cho bạn trực tiếp nguyên nhân của vấn đề.


1
@SMR, trong trường hợp này, phần thiết yếu của câu trả lời là toàn bộ, trang lớn, được liên kết. Vì vậy, chỉ bao gồm liên kết trong câu trả lời là hoàn toàn tốt. Một vài lời về lý do tại sao tác giả thích Valgrind hơn gdb và cách anh ấy giải quyết vấn đề cụ thể là IMHO thực sự thiếu điều gì trong câu trả lời.
ndemou

20

Ba quy tắc cơ bản:

  1. Đặt con trỏ thành NULLsau khi rảnh
  2. Kiểm tra NULLtrước khi giải phóng.
  3. Khởi tạo con trỏ đến NULLkhi bắt đầu.

Sự kết hợp của ba hoạt động khá tốt.


1
Tôi không phải chuyên gia về C, nhưng tôi thường có thể giữ đầu mình trên mặt nước. Tại sao # 1? Có phải nó chỉ để chương trình của bạn không bị treo khi bạn thử và truy cập vào một con trỏ miễn phí, và không chỉ là lỗi im lặng?
Daniel Harms

1
@Preetch: Vâng, đó là vấn đề. Đó là một thực tiễn tốt: có một con trỏ đến bộ nhớ bị xóa là một rủi ro.
vào

10
Nói một cách chính xác, tôi nghĩ # 2 là không cần thiết vì hầu hết các trình biên dịch sẽ cho phép bạn cố gắng xóa một con trỏ null mà không gây ra sự cố. Tôi chắc rằng ai đó sẽ sửa cho tôi nếu tôi sai. :)
Thành phần 10

11
@ Thành phần10 Tôi nghĩ rằng giải phóng NULL là yêu cầu của tiêu chuẩn C để không làm gì cả.
Demi

2
@Demetri: Đúng, bạn nói đúng "nếu giá trị của toán hạng xóa là con trỏ null thì hoạt động không có hiệu lực." (ISO / IEC 14882: 2003 (E) 5.3.5.2)
Thành phần 10

15

Bạn có thể sử dụng valgrindđể gỡ lỗi nó.

#include<stdio.h>
#include<stdlib.h>

int main()
{
 char *x = malloc(100);
 free(x);
 free(x);
 return 0;
}

[sand@PS-CNTOS-64-S11 testbox]$ vim t1.c
[sand@PS-CNTOS-64-S11 testbox]$ cc -g t1.c -o t1
[sand@PS-CNTOS-64-S11 testbox]$ ./t1
*** glibc detected *** ./t1: double free or corruption (top): 0x00000000058f7010 ***
======= Backtrace: =========
/lib64/libc.so.6[0x3a3127245f]
/lib64/libc.so.6(cfree+0x4b)[0x3a312728bb]
./t1[0x400500]
/lib64/libc.so.6(__libc_start_main+0xf4)[0x3a3121d994]
./t1[0x400429]
======= Memory map: ========
00400000-00401000 r-xp 00000000 68:02 30246184                           /home/sand/testbox/t1
00600000-00601000 rw-p 00000000 68:02 30246184                           /home/sand/testbox/t1
058f7000-05918000 rw-p 058f7000 00:00 0                                  [heap]
3a30e00000-3a30e1c000 r-xp 00000000 68:03 5308733                        /lib64/ld-2.5.so
3a3101b000-3a3101c000 r--p 0001b000 68:03 5308733                        /lib64/ld-2.5.so
3a3101c000-3a3101d000 rw-p 0001c000 68:03 5308733                        /lib64/ld-2.5.so
3a31200000-3a3134e000 r-xp 00000000 68:03 5310248                        /lib64/libc-2.5.so
3a3134e000-3a3154e000 ---p 0014e000 68:03 5310248                        /lib64/libc-2.5.so
3a3154e000-3a31552000 r--p 0014e000 68:03 5310248                        /lib64/libc-2.5.so
3a31552000-3a31553000 rw-p 00152000 68:03 5310248                        /lib64/libc-2.5.so
3a31553000-3a31558000 rw-p 3a31553000 00:00 0
3a41c00000-3a41c0d000 r-xp 00000000 68:03 5310264                        /lib64/libgcc_s-4.1.2-20080825.so.1
3a41c0d000-3a41e0d000 ---p 0000d000 68:03 5310264                        /lib64/libgcc_s-4.1.2-20080825.so.1
3a41e0d000-3a41e0e000 rw-p 0000d000 68:03 5310264                        /lib64/libgcc_s-4.1.2-20080825.so.1
2b1912300000-2b1912302000 rw-p 2b1912300000 00:00 0
2b191231c000-2b191231d000 rw-p 2b191231c000 00:00 0
7ffffe214000-7ffffe229000 rw-p 7ffffffe9000 00:00 0                      [stack]
7ffffe2b0000-7ffffe2b4000 r-xp 7ffffe2b0000 00:00 0                      [vdso]
ffffffffff600000-ffffffffffe00000 ---p 00000000 00:00 0                  [vsyscall]
Aborted
[sand@PS-CNTOS-64-S11 testbox]$


[sand@PS-CNTOS-64-S11 testbox]$ vim t1.c
[sand@PS-CNTOS-64-S11 testbox]$ cc -g t1.c -o t1
[sand@PS-CNTOS-64-S11 testbox]$ valgrind --tool=memcheck ./t1
==20859== Memcheck, a memory error detector
==20859== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==20859== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
==20859== Command: ./t1
==20859==
==20859== Invalid free() / delete / delete[]
==20859==    at 0x4A05A31: free (vg_replace_malloc.c:325)
==20859==    by 0x4004FF: main (t1.c:8)
==20859==  Address 0x4c26040 is 0 bytes inside a block of size 100 free'd
==20859==    at 0x4A05A31: free (vg_replace_malloc.c:325)
==20859==    by 0x4004F6: main (t1.c:7)
==20859==
==20859==
==20859== HEAP SUMMARY:
==20859==     in use at exit: 0 bytes in 0 blocks
==20859==   total heap usage: 1 allocs, 2 frees, 100 bytes allocated
==20859==
==20859== All heap blocks were freed -- no leaks are possible
==20859==
==20859== For counts of detected and suppressed errors, rerun with: -v
==20859== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 4 from 4)
[sand@PS-CNTOS-64-S11 testbox]$


[sand@PS-CNTOS-64-S11 testbox]$ valgrind --tool=memcheck --leak-check=full ./t1
==20899== Memcheck, a memory error detector
==20899== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==20899== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
==20899== Command: ./t1
==20899==
==20899== Invalid free() / delete / delete[]
==20899==    at 0x4A05A31: free (vg_replace_malloc.c:325)
==20899==    by 0x4004FF: main (t1.c:8)
==20899==  Address 0x4c26040 is 0 bytes inside a block of size 100 free'd
==20899==    at 0x4A05A31: free (vg_replace_malloc.c:325)
==20899==    by 0x4004F6: main (t1.c:7)
==20899==
==20899==
==20899== HEAP SUMMARY:
==20899==     in use at exit: 0 bytes in 0 blocks
==20899==   total heap usage: 1 allocs, 2 frees, 100 bytes allocated
==20899==
==20899== All heap blocks were freed -- no leaks are possible
==20899==
==20899== For counts of detected and suppressed errors, rerun with: -v
==20899== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 4 from 4)
[sand@PS-CNTOS-64-S11 testbox]$

Một cách sửa chữa có thể:

#include<stdio.h>
#include<stdlib.h>

int main()
{
 char *x = malloc(100);
 free(x);
 x=NULL;
 free(x);
 return 0;
}

[sand@PS-CNTOS-64-S11 testbox]$ vim t1.c
[sand@PS-CNTOS-64-S11 testbox]$ cc -g t1.c -o t1
[sand@PS-CNTOS-64-S11 testbox]$ ./t1
[sand@PS-CNTOS-64-S11 testbox]$

[sand@PS-CNTOS-64-S11 testbox]$ valgrind --tool=memcheck --leak-check=full ./t1
==20958== Memcheck, a memory error detector
==20958== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==20958== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
==20958== Command: ./t1
==20958==
==20958==
==20958== HEAP SUMMARY:
==20958==     in use at exit: 0 bytes in 0 blocks
==20958==   total heap usage: 1 allocs, 1 frees, 100 bytes allocated
==20958==
==20958== All heap blocks were freed -- no leaks are possible
==20958==
==20958== For counts of detected and suppressed errors, rerun with: -v
==20958== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 4 from 4)
[sand@PS-CNTOS-64-S11 testbox]$

Kiểm tra blog bằng cách sử dụng Liên kết Valgrind


Chương trình của tôi mất khoảng 30 phút để chạy, trên Valgrind, có thể mất 18 đến 20 giờ để hoàn thành.
Kemin Zhou

12

Với các trình biên dịch C ++ hiện đại, bạn có thể sử dụng trình vệ sinh để theo dõi.

Ví dụ mẫu:

Chương trình của tôi:

$cat d_free.cxx 
#include<iostream>

using namespace std;

int main()

{
   int * i = new int();
   delete i;
   //i = NULL;
   delete i;
}

Biên dịch với trình khử trùng địa chỉ:

# g++-7.1 d_free.cxx -Wall -Werror -fsanitize=address -g

Hành hình :

# ./a.out 
=================================================================
==4836==ERROR: AddressSanitizer: attempting double-free on 0x602000000010 in thread T0:
    #0 0x7f35b2d7b3c8 in operator delete(void*, unsigned long) /media/sf_shared/gcc-7.1.0/libsanitizer/asan/asan_new_delete.cc:140
    #1 0x400b2c in main /media/sf_shared/jkr/cpp/d_free/d_free.cxx:11
    #2 0x7f35b2050c04 in __libc_start_main (/lib64/libc.so.6+0x21c04)
    #3 0x400a08  (/media/sf_shared/jkr/cpp/d_free/a.out+0x400a08)

0x602000000010 is located 0 bytes inside of 4-byte region [0x602000000010,0x602000000014)
freed by thread T0 here:
    #0 0x7f35b2d7b3c8 in operator delete(void*, unsigned long) /media/sf_shared/gcc-7.1.0/libsanitizer/asan/asan_new_delete.cc:140
    #1 0x400b1b in main /media/sf_shared/jkr/cpp/d_free/d_free.cxx:9
    #2 0x7f35b2050c04 in __libc_start_main (/lib64/libc.so.6+0x21c04)

previously allocated by thread T0 here:
    #0 0x7f35b2d7a040 in operator new(unsigned long) /media/sf_shared/gcc-7.1.0/libsanitizer/asan/asan_new_delete.cc:80
    #1 0x400ac9 in main /media/sf_shared/jkr/cpp/d_free/d_free.cxx:8
    #2 0x7f35b2050c04 in __libc_start_main (/lib64/libc.so.6+0x21c04)

SUMMARY: AddressSanitizer: double-free /media/sf_shared/gcc-7.1.0/libsanitizer/asan/asan_new_delete.cc:140 in operator delete(void*, unsigned long)
==4836==ABORTING

Để tìm hiểu thêm về chất khử trùng, bạn có thể kiểm tra cái này hoặc cái này hoặc bất kỳ tài liệu trình biên dịch c ++ hiện đại nào (ví dụ: gcc, clang, v.v.).


5

Bạn có đang sử dụng các con trỏ thông minh như Boost shared_ptrkhông? Nếu có, hãy kiểm tra xem bạn có đang trực tiếp sử dụng con trỏ thô ở bất kỳ đâu bằng cách gọi get(). Tôi thấy đây là một vấn đề khá phổ biến.

Ví dụ: hãy tưởng tượng một tình huống trong đó một con trỏ thô được chuyển (có thể là một trình xử lý gọi lại, chẳng hạn) tới mã của bạn. Bạn có thể quyết định gán con trỏ này cho một con trỏ thông minh để đối phó với việc đếm tham chiếu, v.v. Sai lầm lớn: mã của bạn không sở hữu con trỏ này trừ khi bạn chụp một bản sao sâu. Khi mã của bạn được thực hiện với con trỏ thông minh, nó sẽ phá hủy nó và cố gắng phá hủy bộ nhớ mà nó trỏ đến vì nó nghĩ rằng không ai khác cần nó, nhưng mã gọi sau đó sẽ cố gắng xóa nó và bạn sẽ nhận được gấp đôi vấn đề miễn phí.

Tất nhiên, đó có thể không phải là vấn đề của bạn ở đây. Đơn giản nhất, đây là một ví dụ cho thấy nó có thể xảy ra như thế nào. Lần xóa đầu tiên là tốt nhưng trình biên dịch cảm thấy rằng nó đã bị xóa bộ nhớ đó và gây ra sự cố. Đó là lý do tại sao việc gán 0 cho một con trỏ ngay sau khi xóa là một ý kiến ​​hay.

int main(int argc, char* argv[])
{
    char* ptr = new char[20];

    delete[] ptr;
    ptr = 0;  // Comment me out and watch me crash and burn.
    delete[] ptr;
}

Chỉnh sửa: được đổi deletethành delete[], vì ptr là một mảng các ký tự.


Tôi đã không sử dụng bất kỳ lệnh xóa nào trong chương trình của mình. Đây có thể vẫn là vấn đề?
neuromancer

1
@Phenom: Tại sao bạn không sử dụng tính năng xóa? Có phải vì bạn đang sử dụng con trỏ thông minh? Có lẽ bạn đang sử dụng mới trong mã của mình để tạo các đối tượng trên heap? Nếu câu trả lời cho cả hai điều này là có thì bạn có đang sử dụng get / set trên con trỏ thông minh để sao chép xung quanh con trỏ thô không? Nếu vậy, đừng! Bạn sẽ phá vỡ việc đếm tham chiếu. Ngoài ra, bạn có thể gán một con trỏ từ mã thư viện mà bạn đang gọi cho một con trỏ thông minh. Nếu bạn không 'sở hữu' bộ nhớ được trỏ đến thì đừng làm điều đó, vì cả thư viện và con trỏ thông minh sẽ cố gắng xóa nó.
Thành phần 10

-2

Tôi biết đây là một chủ đề rất cũ, nhưng nó là tìm kiếm hàng đầu của google cho lỗi này và không có câu trả lời nào đề cập đến nguyên nhân phổ biến của lỗi.

Đó là đóng một tệp bạn đã đóng.

Nếu bạn không chú ý và có hai chức năng khác nhau đóng cùng một tệp, thì chức năng thứ hai sẽ tạo ra lỗi này.


Bạn không chính xác, lỗi này xảy ra do lỗi kép, chính xác như trạng thái lỗi. Thực tế là bạn đang đóng một tệp hai lần gây ra sự trống gấp đôi vì rõ ràng phương pháp đóng đang cố gắng giải phóng cùng một dữ liệu hai lần.
Geoffrey
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.