对于“C++编译器无法判断是否应该在当前编译单元生成vtable定义”的理解
成员虚函数重复数据
当在头文件中定义了一个类,且该类具有虚函数时。正常情况来说,它的vtable应该在编译单元(.cpp)中被定义,但是若编写代码时,在头文件中就定义了虚函数,那么此时所有虚函数都是inline的。那么编译器不知道翻译哪一个编译单元中生成vtable,为了保险起见,那么只能每个编译单元都生成vtable,然后交由链接器来消除重复数据。
header.h
#ifndef HEADER_H
#define HEADER_H
class Base
{
public:
virtual void doit(){};
virtual ~Base(){};
};
#endif
hello.cpp
#include "header.h"
//void Base::doit(){};
int main()
{
Base* b = new Base;
b->doit();
return 0;
}
base.cpp
#include "header.h"
void fun(Base B)
{
Base ff;
B.doit();
}
执行命令
g++ -c base.cc hello.cc
nm hello.o base.o | c++filt
g++ base.cc hello.cc
nm a.out | c++filt
生成
//nm hello.o base.o | c++filt
hello.o:
0000000000000000 T main
U operator delete(void*)
0000000000000000 W Base::doit()
0000000000000000 W Base::Base()
0000000000000000 W Base::Base()
0000000000000000 n Base::Base()
0000000000000000 W Base::~Base()
0000000000000000 W Base::~Base()
0000000000000000 W Base::~Base()
0000000000000000 n Base::~Base()
U operator new(unsigned long)
0000000000000000 V typeinfo for Base
0000000000000000 V typeinfo name for Base
0000000000000000 V vtable for Base
U vtable for __cxxabiv1::__class_type_info
base.o:
U __stack_chk_fail
0000000000000000 T fun(Base)
U operator delete(void*)
0000000000000000 W Base::doit()
0000000000000000 W Base::Base()
0000000000000000 W Base::Base()
0000000000000000 n Base::Base()
0000000000000000 W Base::~Base()
0000000000000000 W Base::~Base()
0000000000000000 W Base::~Base()
0000000000000000 n Base::~Base()
0000000000000000 V typeinfo for Base
0000000000000000 V typeinfo name for Base
0000000000000000 V vtable for Base
U vtable for __cxxabiv1::__class_type_info
//nm a.out |c++filt
0000000000400716 T fun(Base)
U operator delete(void*)@@GLIBCXX_3.4
000000000040076c W Base::doit()
00000000004007ce W Base::Base()
00000000004007ce W Base::Base()
00000000004007a8 W Base::~Base()
0000000000400778 W Base::~Base()
0000000000400778 W Base::~Base()
U operator new(unsigned long)@@GLIBCXX_3.4
00000000004008e0 V typeinfo for Base
00000000004008f0 V typeinfo name for Base
00000000004008b8 V vtable for Base
0000000000601040 V vtable for __cxxabiv1::__class_type_info@@CXXABI_1.3
当把doit()函数和Base析构函数移到hello.cpp中实现时。
//hello.cpp
#include "header.h"
void Base::doit(){};
Base::~Base(){};
//Base::Base(){};
int main()
{
Base* b = new Base;
b->doit();
return 0;
}
//header.h
#ifndef HEADER_H
#define HEADER_H
class Base
{
public:
virtual ~Base();
virtual void doit();
//virtual ~Base();
};
#endif
执行命令后
//nm hello.o base.o | c++filt
hello.o:
0000000000000062 T main
U operator delete(void*)
0000000000000000 T Base::doit()
0000000000000000 W Base::Base()
0000000000000000 W Base::Base()
0000000000000000 n Base::Base()
000000000000003c T Base::~Base()
000000000000000c T Base::~Base()
000000000000000c T Base::~Base()
U operator new(unsigned long)
0000000000000000 V typeinfo for Base
0000000000000000 V typeinfo name for Base
0000000000000000 V vtable for Base
U vtable for __cxxabiv1::__class_type_info
base.o:
U __gxx_personality_v0
U __stack_chk_fail
U _Unwind_Resume
0000000000000000 T fun(Base)
U Base::doit()
0000000000000000 W Base::Base()
0000000000000000 W Base::Base()
0000000000000000 n Base::Base()
U Base::~Base()
U vtable for Base
对比可以发现vtable、doit()、析构函数的定义都在hello.cpp中了。进一步在hello.cpp中实现构造函数后,base.o中只剩下一个fun()函数的定义了。故成员函数的定义放在源文件中去,这样可以减少.o文件的大小,加快链接速度。注意此时vtable定义在hello.o文件中。
两种情况下生成文件的大小比较。
//size hello.o base.o a.out
//函数在.h文件中定义
text data bss dec hex filename
434 0 0 434 1b2 hello.o
451 0 0 451 1c3 base.o
2230 584 96 2910 b5e a.out
//size hello.o base.o a.out
//函数在.cpp文件中定义
text data bss dec hex filename
436 0 0 436 1b4 hello.o
287 0 0 287 11f base.o
2544 616 96 3256 cb8 a.out
有关out-line虚函数
在类内生命一个非inline函数(函数在别处定义),且非纯虚的虚函数,那么这个虚函数中的第一个out-line函数会被当作关键方法,那么vtable就会在该虚函数实现的cpp文件中定义。上述文件中,out-line函数为doit()函数,故vtable在hello.o文件中实现。
把虚构函数移到base.cpp中,
hello.o:
000000000000000b T main
0000000000000000 T Base::doit()
0000000000000000 W Base::Base()
0000000000000000 W Base::Base()
0000000000000000 n Base::Base()
U operator new(unsigned long)
U vtable for Base
base.o:
U __gxx_personality_v0
U __stack_chk_fail
U _Unwind_Resume
0000000000000056 T fun(Base)
U operator delete(void*)
U Base::doit()
0000000000000000 W Base::Base()
0000000000000000 W Base::Base()
0000000000000000 n Base::Base()
0000000000000030 T Base::~Base()
0000000000000000 T Base::~Base()
0000000000000000 T Base::~Base()
0000000000000000 V typeinfo for Base
0000000000000000 V typeinfo name for Base
0000000000000000 V vtable for Base
U vtable for __cxxabiv1::__class_type_info
vtable也转移到base.o文件中。且out-line函数是指文件类中第一个非类内实现的函数。
header.h改为
#ifndef HEADER_H
#define HEADER_H
class Base
{
public:
virtual ~Base(){};
virtual void doit();
virtual void process();
//virtual ~Base();
};
#endif
在base.cpp()中实现doit(),hello.cpp中实现process()。vtable在实现doit()的文件中。
hello.o:
000000000000000b T main
U operator delete(void*)
0000000000000000 T Base::doit()
U Base::process()
0000000000000000 W Base::Base()
0000000000000000 W Base::Base()
0000000000000000 n Base::Base()
0000000000000000 W Base::~Base()
0000000000000000 W Base::~Base()
0000000000000000 W Base::~Base()
0000000000000000 n Base::~Base()
U operator new(unsigned long)
0000000000000000 V typeinfo for Base
0000000000000000 V typeinfo name for Base
0000000000000000 V vtable for Base
U vtable for __cxxabiv1::__class_type_info
base.o:
U __gxx_personality_v0
U __stack_chk_fail
U _Unwind_Resume
000000000000000b T fun(Base)
U operator delete(void*)
U Base::doit()
0000000000000000 T Base::process()
0000000000000000 W Base::Base()
0000000000000000 W Base::Base()
0000000000000000 n Base::Base()
0000000000000000 W Base::~Base()
0000000000000000 W Base::~Base()
0000000000000000 W Base::~Base()
0000000000000000 n Base::~Base()
U vtable for Base