2021-09-18

对于“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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值