C++虚函数表底层结构

C++虚函数表底层结构

C++ 通过虚函数指针和虚函数表实现多态,但是具体底层是如何实现的呢。可以通过下面这个例子来分析

#include <iostream>
using namespace std;

class base{
    virtual void fun1(){ cout << "i am base fun1." << endl; }
    virtual void fun2(){ cout << "i am base fun2." << endl; }
};

class other: public base {
    virtual void fun1(){ cout << "i am other fun1." << endl; }
    virtual void fun3(){ cout << "i am other fun3." << endl; }
};

int main(){
    other a;
}

不知道为什么我无法使用gcc -fdump-class-hierarchy test.cpp命令(也许是新的gcc13.2已经弃用了该命令),我在Windows和Linux下尝试都无法使用该命令,因此我这里使用的是替换的-fdump-lang-class

gcc -dfump-lang-class test.cpp

生成新文件a-test.cpp.001l.class,打开找到如下片段

·············
Vtable for base
base::_ZTV4base: 4 entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI4base)
16    (int (*)(...))base::fun1
24    (int (*)(...))base::fun2

Class base
   size=8 align=8
   base size=8 base align=8
base (0x0x469b960) 0 nearly-empty
    vptr=((& base::_ZTV4base) + 16)

·············

Vtable for other
other::_ZTV5other: 5 entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI5other)
16    (int (*)(...))other::fun1
24    (int (*)(...))base::fun2
32    (int (*)(...))other::fun3

Class other
   size=8 align=8
   base size=8 base align=8
other (0x0x46abaf8) 0 nearly-empty
    vptr=((& other::_ZTV5other) + 16)
base (0x0x469bc00) 0 nearly-empty
      primary-for other (0x0x46abaf8)

可以看到,other类继承了base的虚表结构,同时可以看到other重写了fun1函数、继承了来自base的fun2函数,添加了other自己的fun3函数。

虚函数指针的个数

虚函数指针的个数与继承关系相关,在如上的demo中,每个对象只具有一个虚函数指针,如果是如下的多重继承关系,那么虚函数指针的个数也会发生相应变化。

#include <iostream>
using namespace std;

class base1 {
    virtual void fun1() {}
    virtual void fun2() {}
};

class base2 {
    virtual void fun3() {}
    virtual void fun4() {}
};

class other : public base1, public base2 {
    virtual void fun1() {}
    virtual void fun3() {}
};

int main() {
    other a;
    return 0;
}
Vtable for other
other::_ZTV5other: 9 entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI5other)
16    (int (*)(...))other::fun1
24    (int (*)(...))base1::fun2
32    (int (*)(...))other::fun3
40    (int (*)(...))-8
48    (int (*)(...))(& _ZTI5other)
56    (int (*)(...))other::_ZThn8_N5other4fun3Ev
64    (int (*)(...))base2::fun4

Class other
   size=16 align=8
   base size=16 base align=8
other (0x0x450d5b0) 0
    vptr=((& other::_ZTV5other) + 16)  // 虚函数指针1
base1 (0x0x4643ba0) 0 nearly-empty
      primary-for other (0x0x450d5b0)
base2 (0x0x4643c00) 8 nearly-empty
      vptr=((& other::_ZTV5other) + 56) // 虚函数指针2

如上可见,other的对象具有两个虚函数指针。

内存布局

请问 虚函数表虚函数 分别在内存的哪个数据分区?实际上具体的存储位置与编辑器、操作系统相关,经过测试,在不同的系统中,存储位置也不同,我们可以使用 objdump 工具进行分析。

使用上面的测试 demo 在Linux Ubuntu下进行分析:虚函数表在内存的 文字常量区,虚函数在内存的 程序代码区

  • 数据分区。
区域 描述 变量类型
stack 栈区 临时变量
heap 堆区 malloc 分配空间的变量
.data,.bss 全局数据区 全局变量/静态变量
.rodata 文字常量区 只读数据,常量等
.text 程序代码区 程序代码
  • objdump 工具使用。
# 编译测试代码。
g++ -std=c++11 test.cpp -o test

# 使用 objdump 导出执行文件的信息。
objdump -CdStT test > asm.log

# 获取程序代码区信息。
cat asm.log| grep '\.text'

0000000000001130 l     F .text  0000000000000000              deregister_tm_clones
0000000000001160 l     F .text  0000000000000000              register_tm_clones
00000000000011a0 l     F .text  0000000000000000              __do_global_dtors_aux
00000000000011e0 l     F .text  0000000000000000              frame_dummy
0000000000001239 l     F .text  0000000000000056              __static_initialization_and_destruction_0(int, int)
000000000000128f l     F .text  0000000000000019              _GLOBAL__sub_I_main
00000000000012a8  w    F .text  000000000000003e              other::fun1()
00000000000011e9 g     F .text  0000000000000050              main
00000000000012e6  w    F .text  000000000000003e              other::fun2()
0000000000001324  w    F .text  000000000000003e              other::fun3()
0000000000001100 g     F .text  0000000000000026              _start

# 获取程序文字常量区信息。
cat asm.log| grep '\.data'
0000000000004010 g       .data  0000000000000000              _edata
0000000000004000  w      .data  0000000000000000              data_start
0000000000004008 g     O .data  0000000000000000              .hidden __dso_handle
0000000000003d70  w    O .data.rel.ro   0000000000000010              typeinfo for base
0000000000003d58  w    O .data.rel.ro   0000000000000018              typeinfo for other
0000000000003d30  w    O .data.rel.ro   0000000000000028              vtable for other

参考:C++虚函数的底层实现原理详解_虚函数底层实现原理-CSDN博客

作者:WuQiling
文章链接:https://www.wqlblog.cn/c虚函数表底层结构/
文章采用 CC BY-NC-SA 4.0 协议进行许可,转载请遵循协议
暂无评论

发送评论 编辑评论


				
默认
贴吧
上一篇
下一篇