2010-07-31 7 views
6

내가 내 질문에 문제 해결 싶지 않아 있습니다 - 뭔가에 대해 궁금 해서요 내가 이렇게 일어나는 일들의 가능성에 대해 생각하고했습니다 :개체를 삭제하면 정확히 어떻게됩니까? (GCC) (때 충돌을 두 번 삭제 하시겠습니까?)

객체에서 삭제하고 gcc를 컴파일러로 사용하면 정확히 어떻게됩니까?

지난 주 나는 경쟁 조건이 개체의 이중 삭제로 이어지는 충돌을 조사하고있었습니다.

가상 함수 테이블에 대한 포인터가 이미 덮어 쓰기 때문에 개체의 가상 소멸자를 호출 할 때 충돌이 발생했습니다.

가상 함수 포인터가 첫 번째 삭제로 덮어 씁니까?

그렇다면 새로운 메모리 할당이 이루어지지 않는 한 두 번째 삭제가 안전합니까?

내가 전에는 인식하지 못했던 유일한 궁금한 점은 가상 함수 표가 첫 번째 삭제 중에 immediatly 덮어 쓰이거나 두 번째 삭제가 중단되지 않는다는 것입니다.

(첫 번째는 "경주"가 발생하면 항상 같은 위치에서 충돌이 발생한다는 것을 의미합니다. 두 번째 경주는 일반적으로 경주가 발생해도 아무 일도 일어나지 않습니다.) 그리고 세 번째 스레드가 한편 문제가 발생합니다)


편집/업데이트 :. 내가 다음 코드 세그먼트 폴트와 함께 테스트를 충돌 않았다

(GCC 4.4는 i686과 AMD64) :

class M 
{ 
private: 
    int* ptr; 
public: 
    M() { 
    ptr = new int[1]; 
    } 
    virtual ~M() {delete ptr;} 
}; 

int main(int argc, char** argv) 
{ 
    M* ptr = new M(); 
    delete ptr; 
    delete ptr; 
} 

dtor에서 '가상'을 제거하면 double-free를 감지하기 때문에 프로그램이 glibc에 의해 중단됩니다. 'virtual'을 사용하면 가상 함수 테이블에 대한 포인터가 유효하지 않기 때문에 소멸자에 간접 함수 호출을 수행 할 때 충돌이 발생합니다.

포인터가 유효한 메모리 영역 (힙)을 가리키고 있지만 유효하지 않은 값 (카운터? 매우 작음, 예 : 0x11 또는 0x21)이므로 '호출'(또는 'jmp '컴파일러가 반환 최적화를했을 때) 잘못된 영역으로 건너 뜁니다.

프로그램 신호 SIGSEGV,

분할 결함을 받았다. 0x0000000000000021

??() (gdb)

# 0 개 0x0000000000000021 in ??()() 주에서

# 1 0x000000000040083e

그래서 위에서 언급 한 조건에 가상 함수 테이블에 대한 포인터는 항상 덮어 쓰기 한 다음 삭제가 열반 경우로 이동합니다 있도록 먼저 삭제 클래스에는 가상 소멸자가 있습니다.

+0

은 당신이 어떤 뮤텍스 또는 중요한 부분에 투자해야 할 것 같은데 너무 –

+0

0A0D : 이것은 내 prelimary 솔루션 (해결 방법)이다. 실제로 개체를 삭제할 수있는 두 개의 스레드가 있다는 의도가 있었기 때문에 디자인 결함이있었습니다. – IanH

답변

6

이것은 메모리 할당 자 자체의 구현에 따라 다르며, 일부 개체의 v- 테이블을 덮어 쓰는 것과 같은 응용 프로그램 종속 오류는 말할 것도 없습니다. 다수의 메모리 할당 자 스키마가 있으며 모두 free() 두 배의 기능과 저항이 있지만 두 가지 모두 하나의 공통 속성을 공유합니다. 응용 프로그램이 두 번째 free() 후 얼마 지나지 않아 충돌합니다.

충돌의 원인은 일반적으로 메모리 할당자가 일부 구현 특정 세부 정보를 저장하기 위해 각 할당 된 메모리 청크 앞에 (헤더) 및 (바닥 글) 전에 작은 양의 메모리를 할당한다는 것입니다. 헤더는 일반적으로 청크의 크기와 다음 청크의 주소를 정의합니다. 바닥 글은 대개 청크 헤더의 포인터입니다. 두 번 삭제하는 것은 적어도 인접한 청크가 비어 있는지 확인하는 것입니다. 따라서 다음과 같은 경우 프로그램이 다운됩니다 :

1) 다음 청크에 대한 포인터를 덮어 쓰고 두 번째 free()는 다음 청크에 액세스하려고 시도 할 때 segfault를 발생시킵니다.

2) 이전 청크의 바닥 글이 수정되어 이전 청크의 헤더에 액세스하면 segfault가 발생합니다.

free()는 여러 위치에서 메모리가 손상되었거나 이미 사용 가능한 청크 중 하나와 겹치는 무료 청크가 추가되어 향후 데이터 손상이 발생할 수 있음을 의미합니다. 결국 프로그램은 손상된 메모리 영역과 관련된 free() 또는 malloc() 중 하나에서 segfault를 발생시킵니다.

+0

삭제 후 더 이상 malloc이 없으면 삭제 된 요소는 어떻게됩니까? – IanH

+0

gcc 4.4에서 몇 가지 테스트를 수행했습니다. 실제로 첫 번째 삭제가 가상 함수 테이블을 덮어 쓰는 것처럼 보이므로 가상 소멸자가 두 번째 호출 될 때 충돌이 발생합니다. 가상 소멸자가 아닌 경우 glibc는 double-free를 감지하고 프로그램을 중단합니다. – IanH

+0

나는 내가 한 테스트와 그 결과를 내 질문에 추가했다. – IanH

6

무언가를 두 번 삭제하는 것은 정의되지 않은 동작입니다. 그 이상의 자세한 설명은 필요하지 않으며 일반적으로 무언가를 찾지 않아도됩니다. 프로그램이 충돌을 일으킬 수도 있지만 그렇지 않을 수도 있습니다.하지만 프로그램을 실행 한 후에는 항상 알 수없는 상태가됩니다.

+2

삭제 된 객체에서 발생할 수있는 일과 코어 덤프 분석에서 도움이되지 않는 것에 대한 지식. – IanH

+0

@Ian 나는 처음부터 코어 덤프를 갖고 싶지 않습니다. 또한 소수의 사람이 수행하는 메모리 할당 시스템에 대해 잘 알고 있지 않으면 컴파일러의 릴리스마다 변경 될 수 있습니다. –

+0

나는 버그를 전혀 가지고 싶지는 않지만, 충돌은 발생한다. 그러면 일어날 수있는 일 (무슨 일이 일어 났는지 이유가 무엇인지)과 그렇지 않은 것을 아는 것은 매우 유용합니다. 장기 실행 프로젝트에서 일반적으로 컴파일러를 자주 변경하지 않습니다. 그리고 컴파일러와 libc는 알지 못하게 메모리 할당자를 변경하지 않기를 바랍니다. – IanH

1

delete을 두 번 (또는 심지어 free) 실행하면 메모리가 이미 다시 할당 될 수 있으며 delete을 다시 실행하면 메모리가 손상 될 수 있습니다. 할당 된 메모리 블록의 크기는 종종 메모리 블록 자체 바로 전에 유지됩니다.

파생 클래스가있는 경우 파생 클래스 (하위)에서 delete를 호출하지 마십시오. 가상으로 선언되지 않은 경우 ~BaseClass() 소멸자 만 호출되어 DerivedClass의 할당 된 메모리가 그대로 유지되고 누출됩니다. 이 경우 DerivedClass에는 해제해야하는 BaseClass의 메모리를 초과하여 할당 된 추가 메모리가 있다고 가정합니다.

BaseClass* obj_ptr = new DerivedClass; // Allowed due to polymorphism. 
... 
delete obj_ptr; // this will call the destructor ~Parent() and NOT ~Child() 
+0

위의 업데이트보기 : 최소한 새로운 gcc를 사용하면 그 사이에 새로운/malloc이 없더라도 즉시 이중 삭제가 중단됩니다. 그리고 소멸자 almos가 항상 가상 일 필요가있는 이유를 압니다. – IanH