it-swarm.dev

Di mana crash "pure virtual function call" berasal?

Saya kadang-kadang melihat program yang macet di komputer saya dengan kesalahan: "panggilan fungsi virtual murni".

Bagaimana program-program ini bahkan mengkompilasi ketika sebuah objek tidak dapat dibuat dari kelas abstrak?

102
Brian R. Bondy

Mereka dapat dihasilkan jika Anda mencoba membuat panggilan fungsi virtual dari konstruktor atau destruktor. Karena Anda tidak dapat membuat panggilan fungsi virtual dari konstruktor atau destruktor (objek kelas turunan belum dibangun atau telah dihancurkan), ia memanggil versi kelas dasar, yang dalam kasus fungsi virtual murni, tidak tidak ada.

(Lihat demo langsung di sini )

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
}
104
Adam Rosenfield

Selain kasus standar dalam memanggil fungsi virtual dari konstruktor atau penghancur objek dengan fungsi virtual murni, Anda juga bisa mendapatkan panggilan fungsi virtual murni (setidaknya di MSVC) jika Anda memanggil fungsi virtual setelah objek dihancurkan . Jelas ini adalah hal yang sangat buruk untuk dicoba dan dilakukan tetapi jika Anda bekerja dengan kelas abstrak sebagai antarmuka dan Anda mengacaukannya maka itu adalah sesuatu yang mungkin Anda lihat. Ini mungkin lebih mungkin jika Anda menggunakan antarmuka terhitung direferensikan dan Anda memiliki bug hitungan ref atau jika Anda memiliki kondisi perlombaan objek/penghancuran objek dalam program multi-berulir ... Hal tentang purecall semacam ini adalah bahwa itu seringkali kurang mudah untuk memahami apa yang terjadi sebagai cek untuk 'tersangka biasa' dari panggilan virtual di ctor dan dtor akan muncul bersih.

Untuk membantu men-debug masalah-masalah semacam ini, Anda dapat, dalam berbagai versi MSVC, mengganti purecall handler pustaka runtime. Anda melakukan ini dengan menyediakan fungsi Anda sendiri dengan tanda tangan ini:

int __cdecl _purecall(void)

dan menautkannya sebelum Anda menautkan pustaka runtime. Ini memberi ANDA kendali atas apa yang terjadi ketika purecall terdeteksi. Setelah Anda memiliki kendali, Anda dapat melakukan sesuatu yang lebih berguna daripada pengendali standar. Saya memiliki seorang pawang yang dapat memberikan jejak tumpukan di mana purecall terjadi; lihat di sini: http://www.lenholgate.com/blog/2006/01/purecall.html untuk lebih jelasnya.

(Catatan Anda juga dapat memanggil _set_purecall_handler () untuk menginstal handler Anda di beberapa versi MSVC).

62
Len Holgate

Biasanya ketika Anda memanggil fungsi virtual melalui penunjuk menggantung - kemungkinan besar instance telah dihancurkan.

Mungkin ada lebih banyak alasan "kreatif": mungkin Anda telah berhasil memotong bagian dari objek Anda di mana fungsi virtual diimplementasikan. Tapi biasanya hanya saja instansinya sudah hancur.

7
Braden

Saya mengalami skenario bahwa fungsi virtual murni dipanggil karena benda yang hancur, Len Holgate sudah memiliki yang sangat bagus jawab , saya ingin menambahkan beberapa warna dengan contoh:

  1. Objek Turunan dibuat, dan pointer (sebagai kelas Base) disimpan di suatu tempat
  2. Objek Berasal dihapus, tetapi entah bagaimana pointer masih dirujuk
  3. Pointer yang menunjuk ke objek Turunan dihapus dipanggil

Destructor kelas turunan mereset poin vptr ke kelas dasar vtable, yang memiliki fungsi virtual murni, jadi ketika kita memanggil fungsi virtual, sebenarnya memanggil ke dalam virutal murni.

Ini bisa terjadi karena bug kode yang jelas, atau skenario kondisi ras yang rumit di lingkungan multi-threading.

Berikut adalah contoh sederhana (g ++ kompilasi dengan optimisasi dimatikan - program sederhana dapat dengan mudah dioptimalkan):

 #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();
 }

Dan jejak tumpukan terlihat seperti:

#0  0x00007ffff7499428 in __GI_raise ([email protected]=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

Sorot:

jika objek dihapus sepenuhnya, artinya destructor dipanggil, dan memroy direklamasi, kita mungkin mendapatkan Segmentation fault karena memori telah kembali ke sistem operasi, dan program tidak dapat mengaksesnya. Jadi skenario "pemanggilan fungsi virtual murni" ini biasanya terjadi ketika objek dialokasikan pada kumpulan memori, sementara suatu objek dihapus, memori yang mendasarinya sebenarnya tidak direklamasi oleh OS, masih ada yang dapat diakses oleh proses.

1
Baiyan Huang

Saya menggunakan VS2010 dan setiap kali saya mencoba memanggil destructor langsung dari metode publik, saya mendapatkan kesalahan "panggilan fungsi virtual murni" selama runtime.

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

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

Jadi saya memindahkan apa yang ada di dalam ~ Foo () untuk memisahkan metode pribadi, kemudian bekerja seperti pesona.

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

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

Jika Anda menggunakan Borland/CodeGear/Embarcadero/Idera C++ Builder, Anda bisa langsung mengimplementasikannya

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

Saat debugging, tempatkan breakpoint dalam kode dan lihat callstack di IDE, jika tidak catat stack panggilan di handler pengecualian Anda (atau fungsi itu) jika Anda memiliki alat yang sesuai untuk itu. Saya pribadi menggunakan MadExcept untuk itu.

PS. Panggilan fungsi asli ada di [C++ Builder]\source\cpprtl\Source\misc\pureerr.cpp

0
Niki

Saya kira ada vtbl yang dibuat untuk kelas abstrak untuk beberapa alasan internal (mungkin diperlukan untuk semacam info jenis run time) dan ada yang salah dan objek nyata mendapatkannya. Itu bug. Itu saja harus mengatakan bahwa sesuatu yang tidak bisa terjadi adalah.

Spekulasi murni

sunting: sepertinya saya salah dalam kasus yang dimaksud. OTOH IIRC beberapa bahasa memang memungkinkan panggilan vtbl keluar dari destruktor konstruktor.

0
BCS