2013-07-08 3 views
6

오늘 내 dll과 실제 프로젝트에서 다른 CRT 설정 (MTd MDd)으로 인해 힙 손상이 발생했습니다. 내가 이상한 것을 발견 한 것은 응용 프로그램이 dll에서 소멸자를 가상으로 설정했을 때만 중단된다는 것입니다. 그 쉬운 설명이 있습니까? 나는 힙에없는 메모리를 해제 할 수 없지만, 소멸자를 가상이 아닌 것으로 정의 할 때 정확히 차이점은 무엇인가.CRT 가상 소멸자

일부 코드는

DLL을

#pragma once 
class CTestClass 
{ 
public: 
    _declspec(dllexport) CTestClass() {}; 
    _declspec(dllexport) virtual ~CTestClass() {}; 
}; 

그리고 내 프로젝트

int main(int argc, char* argv[]) 
{ 
    CTestClass *foo = new CTestClass; 
    delete foo; // Crashes if the destructor is virtual but works if it's not 
} 
+0

또한 declspec을 * class * ('class _declspec (dllexport) CTestClass {...}')로 이동하고 멤버 별 declspecs를 제거하여 동일한 문제가 있습니까? 그냥 궁금해서. 그리고 호출 코드와 DLL은 동일한 CRT (디버그 또는 릴리스)를 사용해야하므로 고려해야 할 사항이 있습니다. 나는 혼합 모드가 지원되는지조차 모르겠다. (나는 그것이 있다고 생각하지 않는다). – WhozCraig

+6

프로세스에 CRT 사본이 여러 개 있습니다. 그리고 v-table이 아니라 클래스 메소드 만 내보내십시오. 이 모든 것이 어떻게 상호 작용하여 코드를 폭파하는지 추론하는 것은 그다지 생산적이지 않습니다.가상 메소드를 사용하여 클래스를 내보내려면 전체 클래스를 내보내고 __declspec (dllexport)를 * class * 키워드 옆에 넣어야합니다. 그리고 객체를 생성하고 소멸시키는 데 하나의 할당자가 사용되는지 확인해야합니다./MD로 꾸준히 빌드하고 똑같은 컴파일러 버전을 사용하지 않는 한 보장하기가 어렵습니다. 모듈 경계를 넘어서 C++ 클래스를 노출하는 것은 위험합니다. –

+0

당신이 옳다는 것은 적절합니다. 왜 그것이 작동하지 않는지를 알아 내더라도, 너무 많이 도와주지 않을 것입니다. 어쨌든 당신의 생각을 주셔서 감사합니다 :) – Poisonbox

답변

2

만이 멤버 함수를 내보낼 컴파일러를 지시 전자의 경우

class CTestClass 
{ 
public: 
    _declspec(dllexport) CTestClass() {} 
    _declspec(dllexport) virtual ~CTestClass() {} 
}; 

__declspec(dllexport) class CTestClass 
{ 
public: 
    CTestClass() {} 
    virtual ~CTestClass() {} 
}; 

차이가있다 : CTestClass :: CTestClass가() 및 CTestClass :: ~ CTestClass(). 하지만 후자의 경우 컴파일러에게 가상 함수 표를 내보내도록 지시합니다. 이 테이블은 일단 가상 소멸자가 있으면 필요합니다. 그래서 그것은 충돌의 원인 일 수 있습니다. 프로그램이 가상 소멸자를 호출하려고하면 관련 가상 함수 테이블에서 해당 소멸자를 찾습니다. 그러나 제대로 초기화되지 않았으므로 어디에서 실제로 가리키는 지 알 수 없습니다. 소멸자가 가상이 아니면 가상 함수 테이블이 필요 없으며 모든 것이 잘 작동합니다.

0

당신은 확실하지 정말 후 충분한 코드로 않았다 조금 명확하게합니다. 나는 당신의 실제 코드에 당신이 연산자는 연산자 새가 DLL의 컨텍스트에서 실행되었다의 개체에 대한 삭제 사용해야합니다 의심

int main(int argc, char* argv[]) 
{ 
    // 1. Allocated an instance of this class in *this/exe* heap, not the DLL's heap 
    // if the constructor allocates memory it will be allocated from the DLL's heap 
    CTestClass *foo = new CTestClass; 

    // 2. Call the destructor, if it calls delete on anything it will be freed from the DLL's heap since thats where the destructor is executing from. Finally we free the foo object from *this/exe* heap - no problems at all. 
    delete foo; 
} 

: 그것과 아무 잘못이 없기 때문에하지만 당신의 예는 충돌이 안됩니다 . 가상 키워드가 없으면 교차 컨텍스트 삭제를 수행하는 소멸자 호출을 놓칠 가능성이 높습니다.

+0

"당신의 예제는 아무 문제가 없기 때문에 충돌하지 않아야합니다."이것은 정확히 내가 생각했던 것입니다. 그러나 코드를 위에서 설명한대로 파기했습니다. 사실 그것은 사실입니다 실패. – Poisonbox

+0

샘플 프로젝트를 업로드 할 수 있습니까? 다른 뭔가 잘못되어 있어야합니다. – paulm

0

상속 계층 트리가있는 경우에만 가상 소멸자가 필요합니다. 가상 키워드는 Vtable에서 소멸자를 찾아 실제 객체 (객체 유형이 아닌)에 대한 포인터가 파괴되는지 확인합니다. 이 예제에서는 CTestClass가 제공 한 코드로 진행하기 때문에 다른 클래스로부터 상속받지는 않습니다.이 클래스는 기본 클래스이므로 가상 소멸자가 필요하지 않습니다. 이 문제를 일으키는 다른 구현 규칙이 있다고 가정하고 있지만 기본 클래스로 가상을 사용하면 안됩니다. 파생 된 객체를 만들 때마다 다형성의 기본을 만들고 기본은 항상 파기됩니다. 파생 된 객체는 가상의 소멸자를 작성하여 런타임 vlookup (가상) 테이블에 배치하면 파기됩니다. .

감사