這問題很有趣, 今天我們假設這樣定義一個header, 他是一個基底模板,
// A.h
#include <string>
class A
{
public:
A(){};
~A(){};
virtual void invoke();protected:
std::string name;
};
假設我們之後定義任意一個object, 即可繼承這個上面的模板, 並且需要實作invoke這個function
// B.h
#include "A.h"
#include <iostream>
class B : public A
{
public:
B(){};
~B(){};
void invoke() override
{
std::cout << "I'm B\n";
};
};
最後我們給一個main.cpp
#include "B.h"int main()
{
B b = B();
b.invoke();
return 0;
}
最後我們編譯他
$g++ main.cpp
/tmp/ccK0skET.o: In function `A::A()':
main.cpp:(.text._ZN1AC2Ev[_ZN1AC5Ev]+0xf): undefined reference to `vtable for A'
/tmp/ccK0skET.o: In function `A::~A()':
main.cpp:(.text._ZN1AD2Ev[_ZN1AD5Ev]+0xf): undefined reference to `vtable for A'
/tmp/ccK0skET.o:(.data.rel.ro._ZTI1B[_ZTI1B]+0x10): undefined reference to `typeinfo for A'
collect2: error: ld returned 1 exit status
很高興能在這邊直接看到編譯器提示我們的錯誤, 很明確地告訴我們vtable並沒有參考依據
問題出在 A.h 中, 我們忘記實作virtual function了
只需要實作, 或是將此virtual 設置為 pure virtual fucntion 即可通行
// A.h
#include <string>
class A
{
public:
A(){};
~A(){};
virtual void invoke() = 0;protected:
std::string name;
};
但問題不僅僅這麼easy, 在某些情況下這樣的行為會被允許?
假設我們今天要做的是動態連結, 下的指令如下
$g++ -fPIC -o libtest.so -shared main.cpp
你可以發現, 順利產生libtest.so 且並沒有任何的副作用, 可是virtual 還是未被reference
於是你開始找到底是哪邊沒有去動態連結到, 但找了一圈發現這跟連結應該無關
我們用nm 這個指令去看
$nm -u libtest.so
U __cxa_atexit@@GLIBC_2.2.5
w __cxa_finalize@@GLIBC_2.2.5
w __gmon_start__
U __gxx_personality_v0@@CXXABI_1.3
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
U __stack_chk_fail@@GLIBC_2.4
U _Unwind_Resume@@GCC_3.0
U _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEC1Ev@@GLIBCXX_3.4.21
U _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEED1Ev@@GLIBCXX_3.4.21
U _ZNSt8ios_base4InitC1Ev@@GLIBCXX_3.4
U _ZNSt8ios_base4InitD1Ev@@GLIBCXX_3.4
U _ZSt4cout@@GLIBCXX_3.4
U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@@GLIBCXX_3.4
U _ZTI1A
U _ZTV1A
U _ZTVN10__cxxabiv120__si_class_type_infoE@@CXXABI_1.3
可以注意到 跟我們有關的是
U _ZTI1A
U _ZTV1A
我們可以用 c++filt 看一下這個undefined的訊息
$ c++filt _ZTI1A
typeinfo for A
跟我們說A的typeinfo 從缺, 比起上面編譯器直接跟我們說少什麼, 這邊能有的資訊就更少了
尤其在大型專案中, 可能很難找到真正定義的位置在哪
會發生這個問題是我在使用pybind, 做c++與python的連結時, 發現so產生都好好的也沒有異狀, 但在import package時就會一直告訴我這個log, 花了一些時間才找到
在此紀錄一下