문제

가끔 다음과 같은 오류로 인해 내 컴퓨터에서 프로그램이 충돌하는 것을 발견했습니다."순수 가상 함수 호출".

추상 클래스로 객체를 생성할 수 없는 경우에도 이러한 프로그램은 어떻게 컴파일합니까?

도움이 되었습니까?

해결책

생성자나 소멸자에서 가상 함수 호출을 시도하면 이러한 문제가 발생할 수 있습니다.생성자나 소멸자에서 가상 함수를 호출할 수 없으므로(파생 클래스 객체가 생성되지 않았거나 이미 삭제됨) 기본 클래스 버전을 호출합니다. 순수 가상 함수의 경우에는 존재하지 않습니다.

(라이브 데모 보기 여기)

class Base
{
public:
    Base() { doIt(); }  // DON'T DO THIS
    virtual void doIt() = 0;
};

void Base::doIt()
{
    std::cout<<"Is it fine to call pure virtual function from constructor?";
}

class Derived : public Base
{
    void doIt() {}
};

int main(void)
{
    Derived d;  // This will cause "pure virtual function call" error
}

다른 팁

순수 가상 함수가 있는 객체의 생성자 또는 소멸자에서 가상 함수를 호출하는 표준 사례뿐만 아니라 객체가 삭제된 후 가상 함수를 호출하면 순수 가상 함수 호출(적어도 MSVC에서는)을 얻을 수도 있습니다. .분명히 이것은 시도하고 수행하기에는 매우 나쁜 일이지만 추상 클래스를 인터페이스로 사용하여 작업하고 엉망이 되면 보게 될 수도 있습니다.참조 카운트 인터페이스를 사용하고 있고 참조 카운트 버그가 있거나 멀티스레드 프로그램에서 객체 사용/객체 파괴 경쟁 조건이 있는 경우 가능성이 더 높습니다.이러한 종류의 순수 호출에 대한 문제는 ctor 및 dtor의 가상 호출에 대한 '일반적인 용의자'에 대한 검사가 깨끗하게 나올 것이기 때문에 무슨 일이 일어나고 있는지 파악하기가 쉽지 않다는 것입니다.

이러한 종류의 문제를 디버깅하는 데 도움을 주기 위해 다양한 버전의 MSVC에서 런타임 라이브러리의 순수 호출 처리기를 바꿀 수 있습니다.이 서명을 사용하여 고유한 기능을 제공하면 됩니다.

int __cdecl _purecall(void)

런타임 라이브러리를 연결하기 전에 연결합니다.이를 통해 순수 호출이 감지될 때 발생하는 일을 사용자가 제어할 수 있습니다.제어권을 갖게 되면 표준 처리기보다 더 유용한 작업을 수행할 수 있습니다.purecall이 발생한 위치에 대한 스택 추적을 제공할 수 있는 핸들러가 있습니다.여기를 보아라: http://www.lenholgate.com/blog/2006/01/purecall.html 상세 사항은.

(일부 MSVC 버전에서는 _set_purecall_handler()를 호출하여 핸들러를 설치할 수도 있습니다.)

일반적으로 매달린 포인터를 통해 가상 함수를 호출하면 인스턴스가 이미 삭제되었을 가능성이 높습니다.

더 "창의적인" 이유도 있을 수 있습니다.어쩌면 가상 기능이 구현된 개체 부분을 잘라내는 데 성공했을 수도 있습니다.그러나 일반적으로 인스턴스가 이미 파괴된 경우가 많습니다.

내부적인 이유로(일종의 런타임 유형 정보에 필요할 수 있음) 추상 클래스용으로 생성된 vtbl이 있고 문제가 발생하여 실제 개체가 이를 가져오는 것 같습니다.버그입니다.그것만으로도 일어날 수 없는 일이 일어난다고 말해야 한다.

순수한 추측

편집하다: 문제의 경우 제가 틀린 것 같습니다.OTOH IIRC 일부 언어에서는 생성자 소멸자에서 vtbl 호출을 허용합니다.

VS2010을 사용하고 있으며 공용 메서드에서 직접 소멸자를 호출하려고 할 때마다 런타임 중에 "순수 가상 함수 호출" 오류가 발생합니다.

template <typename T>
class Foo {
public:
  Foo<T>() {};
  ~Foo<T>() {};

public:
  void SomeMethod1() { this->~Foo(); }; /* ERROR */
};

그래서 ~Foo() 내부의 내용을 개인 메소드를 분리하도록 옮겼고, 그러면 매력처럼 작동했습니다.

template <typename T>
class Foo {
public:
  Foo<T>() {};
  ~Foo<T>() {};

public:
  void _MethodThatDestructs() {};
  void SomeMethod1() { this->_MethodThatDestructs(); }; /* OK */
};

Borland/CodeGear/Embarcadero/Idera C++ Builder를 사용하는 경우 간단히 구현할 수 있습니다.

extern "C" void _RTLENTRY _pure_error_()
{
    //_ErrorExit("Pure virtual function called");
    throw Exception("Pure virtual function called");
}

디버깅하는 동안 코드에 중단점을 배치하고 IDE에서 호출 스택을 확인하세요. 그렇지 않은 경우 적절한 도구가 있는 경우 예외 처리기(또는 해당 함수)에 호출 스택을 기록하세요.저는 개인적으로 MadExcept를 사용합니다.

추신.원래 함수 호출은 [C++ Builder]\source\cpprtl\Source\misc\pureerr.cpp에 있습니다.

파괴된 객체로 인해 순수 가상 함수가 호출되는 시나리오에 직면했습니다. Len Holgate 이미 아주 좋은 대답이 있습니다. 예를 들어 색상을 추가하고 싶습니다.

  1. 파생 된 객체가 생성되고 포인터 (기본 클래스)가 어딘가에 저장됩니다.
  2. 파생 된 객체가 삭제되었지만 어떻게 든 포인터는 여전히 참조됩니다.
  3. 삭제 된 파생 객체를 가리키는 포인터는 호출됩니다.

Derived 클래스 소멸자는 vptr 지점을 순수 가상 함수가 있는 기본 클래스 vtable로 재설정하므로 가상 함수를 호출하면 실제로 순수 가상 함수를 호출합니다.

이는 명백한 코드 버그 또는 멀티스레딩 환경의 복잡한 경쟁 조건 시나리오로 인해 발생할 수 있습니다.

다음은 간단한 예입니다(최적화가 꺼진 상태에서 g++ 컴파일 - 간단한 프로그램은 쉽게 최적화될 수 있음).

 #include <iostream>
 using namespace std;

 char pool[256];

 struct Base
 {
     virtual void foo() = 0;
     virtual ~Base(){};
 };

 struct Derived: public Base
 {
     virtual void foo() override { cout <<"Derived::foo()" << endl;}
 };

 int main()
 {
     auto* pd = new (pool) Derived();
     Base* pb = pd;
     pd->~Derived();
     pb->foo();
 }

스택 추적은 다음과 같습니다.

#0  0x00007ffff7499428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
#1  0x00007ffff749b02a in __GI_abort () at abort.c:89
#2  0x00007ffff7ad78f7 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3  0x00007ffff7adda46 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4  0x00007ffff7adda81 in std::terminate() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#5  0x00007ffff7ade84f in __cxa_pure_virtual () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#6  0x0000000000400f82 in main () at purev.C:22

가장 밝은 부분:

객체가 완전히 삭제되어 소멸자가 호출되고 메모리가 회수되면 간단히 다음을 얻을 수 있습니다. Segmentation fault 메모리가 운영 체제로 반환되어 프로그램이 해당 메모리에 액세스할 수 없기 때문입니다.따라서 이 "순수 가상 함수 호출" 시나리오는 일반적으로 개체가 메모리 풀에 할당될 때 발생합니다. 개체가 삭제되는 동안 기본 메모리는 실제로 OS에서 회수되지 않으며 여전히 프로세스에서 액세스할 수 있습니다.

여기에 그 일이 일어나는 비열한 방법이 있습니다.오늘 나에게 이런 일이 실제로 일어났습니다.

class A
{
  A *pThis;
  public:
  A()
   : pThis(this)
  {
  }

  void callFoo()
  {
    pThis->foo(); // call through the pThis ptr which was initialized in the constructor
  }

  virtual void foo() = 0;
};

class B : public A
{
public:
  virtual void foo()
  {
  }
};

B b();
b.callFoo();
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top