虚函数在哪里使用 vpointers to vtables 来解析方法调用,非虚方法存储在哪里以及它们是如何解析的?
Where virtual functions use vpointers to vtables to resolve the method call, where are non-virtual methods stored and how are they resolved?
A class 定义
class A
{
void AFunc1(){}
void AFunc2(){}
void AFunc3(){}
virtual void AVirtualFunc1(){}
};
将具有 4 个字节的 sizeof()
值,因为指向共享 vtable
的隐藏 vpointer
成员具有指向方法的指针。
但是,class
的一个实例
class B
{
void BFunc1(){}
void BFunc2(){}
void BFunc3(){}
};
将只有 1 个字节的 sizeof()
值,因为不需要 vpointer
,而且也不存在 vtable
。如果是这样,函数 BFunc1()
、BFunc2()
和 BFunc3()
存储在哪里,对象实例如何引用它们?
对于每一个真正在某处使用的非虚函数,函数的代码被发送到目标文件,如果代码来自包含的头文件并在多个翻译单元中使用,则可能在多个文件中。此外,符号也被插入到目标文件中。如果您是这样的 linux 用户,您可以使用 nm
查看已定义 methods/fuctions/... 的 table:
nm main.o |c++filt
U __cxa_atexit
U __dso_handle
0000000000000062 t _GLOBAL__sub_I_main
0000000000000000 T main
0000000000000024 t __static_initialization_and_destruction_0(int, int)
0000000000000000 W A::AVirtualFunc1()
0000000000000000 W A::AFunc1()
0000000000000000 r __gnu_cxx::__default_lock_policy
U std::ios_base::Init::Init()
U std::ios_base::Init::~Init()
0000000000000000 b std::__ioinit
0000000000000000 V typeinfo for A
0000000000000000 V typeinfo name for A
0000000000000000 V vtable for A
U vtable for __cxxabiv1::__class_type_info
如您所见,A::AFunc1()
被定义为 weak
符号。它被定义为弱,因为我们可以在不同的翻译单元中有多个实例,但我们知道,它们都是相同的。 (这就是我们在 C++ 中使用 "One Definition Rule" 的原因)。顺便说一句:如果你有多个使用相同标签的定义,linker 也只放置一个并且没有错误消息。您的程序的行为由 link 命令定义。非常糟糕的事情!回到弱符号,如果我们多次将相同的方法存储在目标文件中,linker 不应该发出错误。 linker 现在可以简单地选择其中之一,而不会出现任何错误消息。例如,main
函数在 table 中被标记为 'T'。如果您多次使用 T
标记相同的符号,linker 将为多个定义的函数发出错误消息。
你看,符号在目标文件中的地址是0000000,也就是说,它目前没有地址。地址将在静态执行 link 时间内或程序启动时的动态 link 时间内重新定位。
如您所见,虚函数的存储方式也是一样的,没有任何区别。也可以使用 A::AVirtualFunc
调用方法本身而无需跳过 vtable.
你还可以看到,vtable 本身是目标文件的一部分,并标记为 V
。您也可以在多个目标文件中使用相同的 vtable。 linker 在最后 link.
中也只拿了其中一个
A class 定义
class A
{
void AFunc1(){}
void AFunc2(){}
void AFunc3(){}
virtual void AVirtualFunc1(){}
};
将具有 4 个字节的 sizeof()
值,因为指向共享 vtable
的隐藏 vpointer
成员具有指向方法的指针。
但是,class
的一个实例class B
{
void BFunc1(){}
void BFunc2(){}
void BFunc3(){}
};
将只有 1 个字节的 sizeof()
值,因为不需要 vpointer
,而且也不存在 vtable
。如果是这样,函数 BFunc1()
、BFunc2()
和 BFunc3()
存储在哪里,对象实例如何引用它们?
对于每一个真正在某处使用的非虚函数,函数的代码被发送到目标文件,如果代码来自包含的头文件并在多个翻译单元中使用,则可能在多个文件中。此外,符号也被插入到目标文件中。如果您是这样的 linux 用户,您可以使用 nm
查看已定义 methods/fuctions/... 的 table:
nm main.o |c++filt
U __cxa_atexit
U __dso_handle
0000000000000062 t _GLOBAL__sub_I_main
0000000000000000 T main
0000000000000024 t __static_initialization_and_destruction_0(int, int)
0000000000000000 W A::AVirtualFunc1()
0000000000000000 W A::AFunc1()
0000000000000000 r __gnu_cxx::__default_lock_policy
U std::ios_base::Init::Init()
U std::ios_base::Init::~Init()
0000000000000000 b std::__ioinit
0000000000000000 V typeinfo for A
0000000000000000 V typeinfo name for A
0000000000000000 V vtable for A
U vtable for __cxxabiv1::__class_type_info
如您所见,A::AFunc1()
被定义为 weak
符号。它被定义为弱,因为我们可以在不同的翻译单元中有多个实例,但我们知道,它们都是相同的。 (这就是我们在 C++ 中使用 "One Definition Rule" 的原因)。顺便说一句:如果你有多个使用相同标签的定义,linker 也只放置一个并且没有错误消息。您的程序的行为由 link 命令定义。非常糟糕的事情!回到弱符号,如果我们多次将相同的方法存储在目标文件中,linker 不应该发出错误。 linker 现在可以简单地选择其中之一,而不会出现任何错误消息。例如,main
函数在 table 中被标记为 'T'。如果您多次使用 T
标记相同的符号,linker 将为多个定义的函数发出错误消息。
你看,符号在目标文件中的地址是0000000,也就是说,它目前没有地址。地址将在静态执行 link 时间内或程序启动时的动态 link 时间内重新定位。
如您所见,虚函数的存储方式也是一样的,没有任何区别。也可以使用 A::AVirtualFunc
调用方法本身而无需跳过 vtable.
你还可以看到,vtable 本身是目标文件的一部分,并标记为 V
。您也可以在多个目标文件中使用相同的 vtable。 linker 在最后 link.