"이중 사용 가능 또는 손상"오류를 추적하는 방법


92

내 (C ++) 프로그램을 실행하면이 오류와 함께 충돌합니다.

* glibc 감지 됨 * ./load : 이중 사용 가능 또는 손상 (! prev) : 0x0000000000c6ed50 ***

오류를 어떻게 추적 할 수 있습니까?

나는 std::cout성공하지 않고 print ( ) 문을 사용해 보았습니다 . 수 gdb이 쉽게?


5
나는 왜 모든 사람들이 NULL포인터를 제안하는지 궁금 하지만 (이 질문이 잘 보여 주듯이 잡힌 오류를 가려주는) 아무도 단순히 수동 메모리 관리를 전혀하지 말라고 제안하지 않습니다. 이것은 C ++에서 매우 가능합니다. 나는 delete몇 년 동안 글을 쓰지 않았습니다 . (그리고 예, 제 코드는 성능이 중요합니다. 그렇지 않으면 C ++로 작성되지 않았을 것입니다.)
sbi

2
@sbi : 힙 손상 등은 거의 발생하지 않습니다. NULLing 포인터로 인해 프로그램이 더 일찍 충돌 할 수 있습니다.
Hasturkun

@Hasturkun : 강력히 동의하지 않습니다. NULL포인터에 대한 주요 인센티브 는 1 초가 delete ptr;터지는 것을 방지하는 것 입니다. 오류를 마스킹하는 것 delete입니다. (포인터가 여전히 유효한 객체를 가리키고 있는지 확인하는데도 사용됩니다. 그러나 이는 가리킬 객체가없는 범위에 포인터가있는 이유에 대한 질문을 제기합니다.)
sbi

답변:


64

glibc를 사용하는 경우 MALLOC_CHECK_환경 변수를로 설정할 수 있습니다 2. 이렇게하면 glibc가 오류 허용 버전을 사용합니다.malloc 하여 이중 해제가 완료되는 지점에서 프로그램이 중단됩니다.

set environment MALLOC_CHECK_ 2프로그램을 실행하기 전에 명령 을 사용하여 gdb에서 설정할 수 있습니다 . 프로그램이 중단되어야하며 free()호출이 백 트레이스에 표시됩니다.

자세한 내용은 man 페이지malloc() 를 참조하십시오.


2
설정은 MALLOC_CHECK_2실제로 내 이중 자유 문제를 해결했습니다 (디버그 모드에서만 해결되지는 않지만)
puk

4
@puk 같은 문제가 있는데 MALLOC_CHECK_를 2로 설정하면 이중 문제가 발생하지 않습니다. 문제를 재현하고 역 추적을 제공하기 위해 코드만큼 적게 삽입 할 수있는 다른 옵션은 무엇입니까?
Wei Zhong

또한 MALLOC_CHECK_를 설정하면 문제를 피할 수 있습니다. 동료 댓글 작성자 / 누군가 ... 문제를 표시하는 다른 방법을 찾았습니까?
pellucidcoder

"MALLOC_CHECK_가 0이 아닌 값으로 설정되면 동일한 인수를 사용한 free의 이중 호출 또는 단일 바이트의 오버런 (off)과 같은 단순한 오류에 대해 허용하도록 설계된 특수 (덜 효율적) 구현이 사용됩니다. 하나씩 버그). " gnu.org/software/libc/manual/html_node/… 그래서 MALLOC_CHECK_는 단순한 메모리 오류를 피하기 위해서만 사용되는 것 같습니다.
pellucidcoder

사실 .... support.microfocus.com/kb/doc.php?id=3113982 는 MALLOC_CHECK_를 3으로 설정하는 것이 가장 유용하고 오류를 감지하는 데 사용할 수있는 것 같습니다.
pellucidcoder

32

두 가지 이상의 가능한 상황이 있습니다.

  1. 동일한 항목을 두 번 삭제합니다.
  2. 할당되지 않은 항목을 삭제하고 있습니다.

첫 번째의 경우 삭제 된 모든 포인터를 NULL로 지정하는 것이 좋습니다.

세 가지 옵션이 있습니다.

  1. 신규 오버로드 및 할당 삭제 및 추적
  2. 예, gdb를 사용하십시오. 그러면 충돌에서 역 추적을 얻을 수 있으며 아마도 매우 유용 할 것입니다.
  3. 제안 된대로-Valgrind를 사용하십시오-시작하기가 쉽지는 않지만 앞으로 수천 배의 시간을 절약 할 수 있습니다.

2. 손상을 일으킬 수 있지만 온 전성 검사는 힙에서만 수행되기 때문에 일반적으로이 메시지가 나타나지는 않습니다. 그러나 나는 3. 힙 버퍼 오버 플로우가 가능하다고 생각한다.
Matthew Flaschen

잘 했어. 사실 나는 포인터를 NULL로 만드는 것을 놓 쳤고이 오류에 직면했습니다. 배운 교훈!
hrushi

26

gdb를 사용할 수 있지만 먼저 Valgrind를 사용해 보겠습니다 . 참고 항목 빠른 시작 가이드 .

간단히 말해서 Valgrind는 프로그램을 계측하여 이중 해제 및 할당 된 메모리 블록의 끝을 지나서 쓰기 (힙을 손상시킬 수 있음)와 같은 동적 할당 메모리를 사용할 때 여러 종류의 오류를 감지 할 수 있습니다. 오류가 발생 하는 즉시이를 감지하고보고 하여 문제의 원인을 직접 가리 킵니다.


1
@SMR,이 경우 대답의 필수 부분은 전체적이고 큰 링크 된 페이지입니다. 따라서 답변에 링크 만 포함하면 완벽합니다. 저자가 gdb보다 Valgrind를 선호하는 이유와 특정 문제를 해결하는 방법에 대한 몇 마디 는 대답에서 실제로 누락 된 IMHO입니다.
ndemou

20

세 가지 기본 규칙 :

  1. 해제 NULL후 포인터 설정
  2. NULL해제하기 전에 확인하십시오 .
  3. NULL처음에 포인터를 초기화 하십시오.

이 세 가지의 조합은 아주 잘 작동합니다.


1
나는 C 전문가는 아니지만 일반적으로 머리를 물 위로 유지할 수 있습니다. 왜 # 1? 무료 포인터에 액세스하려고 할 때 프로그램이 평평하게 충돌하는 것이지 조용한 오류가 아닌가?
Daniel Harms

1
@Precision : 예, 그게 요점입니다. 그것은 좋은 습관입니다 : 삭제 된 메모리에 대한 포인터를 갖는 것은 위험합니다.
ereOn

10
엄밀히 말하면 대부분의 컴파일러에서 문제를 일으키지 않고 널 포인터를 삭제할 수 있으므로 # 2는 불필요하다고 생각합니다. 내가 틀렸다면 누군가 나를 고칠 것이라고 확신합니다. :)
구성 요소 10

11
@ Component10 C 표준에서 아무것도하지 않으려면 NULL을 해제해야한다고 생각합니다.
Demi

2
@Demetri : 네, 맞습니다. "삭제 피연산자의 값이 널 포인터이면 연산은 효과가 없습니다." (ISO / IEC 14882 : 2003 (E) 5.3.5.2)
구성 요소 10

15

valgrind디버깅하는 데 사용할 수 있습니다 .

#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]$

한 가지 가능한 수정 :

#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]$

Valgrind Link 사용에 대한 블로그를 확인하십시오.


내 프로그램을 실행하는 데 약 30 분이 걸리고 Valgrind에서는 완료하는 데 18 ~ 20 시간이 걸릴 수 있습니다.
Kemin Zhou

11

최신 C ++ 컴파일러를 사용하면 새니 타이 저를 사용할 수 있습니다. 를 하여 추적 .

샘플 예 :

내 프로그램 :

$cat d_free.cxx 
#include<iostream>

using namespace std;

int main()

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

주소 새니 타이 저로 컴파일 :

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

실행 :

# ./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

새니 타이 저에 대해 자세히 알아 보려면 this 또는 this 또는 최신 C ++ 컴파일러 (예 : gcc, clang 등) 문서를 확인하세요.


5

Boost와 같은 스마트 포인터를 사용하고 shared_ptr있습니까? 그렇다면 다음을 호출하여 어디에서나 원시 포인터를 직접 사용하고 있는지 확인하십시오.get() . 나는 이것이 매우 일반적인 문제라는 것을 발견했습니다.

예를 들어 원시 포인터가 코드에 전달되는 시나리오 (예 : 콜백 핸들러)를 상상해보십시오. 참조 카운팅 등에 대처하기 위해 이것을 스마트 포인터에 할당하기로 결정할 수 있습니다. 큰 실수 : 깊은 복사를하지 않는 한 코드는이 포인터를 소유하지 않습니다. 스마트 포인터로 코드가 완료되면 다른 사람이 필요하지 않다고 생각 하기 때문에 그것을 파괴하고 가리키는 메모리를 파괴하려고 시도 하지만 호출 코드는 그것을 삭제하려고 시도하고 두 배의 무료 문제.

물론 그것은 여기서 당신의 문제가 아닐 수도 있습니다. 가장 간단합니다. 여기에 그것이 어떻게 일어날 수 있는지 보여주는 예가 있습니다. 첫 번째 삭제는 괜찮지 만 컴파일러는 이미 해당 메모리가 삭제되어 문제를 일으킨다는 것을 감지합니다. 그렇기 때문에 삭제 직후 포인터에 0을 할당하는 것이 좋습니다.

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;
}

편집 : 변경 deletedelete[]PTR은 문자의 배열이기 때문에.


내 프로그램에서 삭제 명령을 사용하지 않았습니다. 이것이 여전히 문제일까요?
neuromancer

1
@Phenom : 왜 삭제를 사용하지 않았습니까? 스마트 포인터를 사용하고 있기 때문입니까? 아마도 코드에서 new를 사용하여 힙에 개체를 만들고 있습니까? 이 두 가지 모두에 대한 대답이 예이면 스마트 포인터에서 get / set을 사용하여 원시 포인터 주위를 복사하고 있습니까? 그렇다면하지 마십시오! 당신은 참조 카운트를 깨뜨릴 것입니다. 또는 호출하는 라이브러리 코드의 포인터를 스마트 포인터에 할당 할 수 있습니다. 가리키는 메모리를 '소유'하지 않으면 라이브러리와 스마트 포인터 모두 삭제를 시도하므로 그렇게하지 마십시오.
Component 10

-2

나는 이것이 매우 오래된 스레드라는 것을 알고 있지만이 오류에 대한 최고의 Google 검색이며 응답 중 어느 것도 오류의 일반적인 원인을 언급하지 않습니다.

이미 닫은 파일을 닫는 것입니다.

주의를 기울이지 않고 두 개의 다른 기능이 동일한 파일을 닫는 경우 두 번째 기능이이 오류를 생성합니다.


당신은 정확하지 않습니다.이 오류는 오류 상태와 마찬가지로 이중 자유로 인해 발생합니다. 파일을 두 번 닫는다는 사실은 close 메서드가 동일한 데이터를 두 번 해제하려고하기 때문에 두 번 해제되는 것입니다.
Geoffrey
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.