对象内存布局

类的内存布局

看下面的程序中,输出的结果会是what?

class Base
{
public:
    //Base();
    ~Base();
    virtual void f() {} 
    static Base origin;
    int a, b, c;
};

int main(int argc, char const *argv[])
{
    printf("%p\n", &Base::a);
    printf("%p\n", &Base::b);
    printf("%p\n", &Base::c);

    Base* base = new Base;
    printf("%p\n", &base->a);
    printf("%p\n", &base->b);
    printf("%p\n", &base->c);

    return 0;
}

运行结果

0x8
0xc
0x10
0x1f4a038
0x1f4a03c
0x1f4a040

分析

Inside C++ Object Model第三章中有这样的介绍:

  • 取一个类的非静态数据成员的地址,将会得到它在class中的offset。
  • 取一个对象的非静态数据成员的地址,将会得到它在内存中的真正地址。

&Base::a = 0x8,在64bit系统下正好是一个指针的大小,Base包含虚函数,可以vptr是在类的起始位置。
虚拟继承

虚基类的两种实现方式

  1. Microsoft编译器引入所谓的virtual base class table,每一个类如果有一个或多个虚基类,就会有编译器安插一个指针,指向虚基类表,而真正的虚基类指针放在表格中。


    clipboard.png
  2. 第二种方法是在virtual function table中放置virtual base class的offset


    clipboard1.png

虚函数表到底在哪

因为在程序里每个类只需要一个vtbl拷贝,所以编译器肯定会遇到一个棘手的问题:
把它放在哪里。大多数程序和程序库由多个object(目标)文件连接而成,但是每个object
文件之间是独立的。哪个object文件应该包含给定类的vtbl呢?你可能会认为放在包含
main函数的object文件里,但是程序库没有main,而且无论如何包含main的源文件不会
涉及很多需要vtbl的类。编译器如何知道它们被要求建立那一个vtbl呢?

  1. 必须采取一种不同的方法,编译器厂商为此分成两个阵营。对于提供集成开发环境(包含编译程序和连接程序)的厂商,一种干脆的方法是为每一个可能需要vtbl的object文件生成一个vtbl拷贝。连接程序然后去除重复的拷贝,在最后的可执行文件或程序库里就为每个vtbl保留一个实例。
  2. 更普通的设计方法是采用启发式算法来决定哪一个object文件应该包含类的vtbl。通常启发式算法是这样的:要在一个object文件中生成一个类的vtbl,要求该object文件包含该类的第一个非内联、非纯虚拟函数(non-inline non-pure virual function)定义(也就是类的实现体)。因此上述C1类的vtbl将被放置到包含C1::~C1定义的object文件里(不是内联的函数),C2类的vtbl被放置到包含C1::~C2定义的object文件里(不是内联函数)。

实际当中,这种启发式算法效果很好。但是如果你过分喜欢声明虚函数为内联函数(参见Effective C++条款33) ,如果在类中的所有虚函数都内声明为内联函数,启发式算法就会失败,大多数基于启发式算法的编译器会在每个使用它的object 文件中生成一个类的vtbl。在大型系统里,这会导致程序包含同一个类的成百上千个vtbl拷贝!大多数遵循这种启发式算法的编译器会给你一些方法来人工控制vtbl的生成,但是一种更好的解决此问题的方法是避免把虚函数声明为内联函数。下面我们将看到,有一些原因导致现在的编译器一般总是忽略虚函数的的inline指令。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。