类的内存布局

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

class Base
{
public:
    //Base();
    ~Base();
    virtual void f() {} 
    static Base origin;
    int a, b, c;
};
int main(int argc, char const *argv[])
{
    printf("%pn", &Base::a);
    printf("%pn", &Base::b);
    printf("%pn", &Base::c);
    Base* base = new Base;
    printf("%pn", &base->a);
    printf("%pn", &base->b);
    printf("%pn", &base->c);
    return 0;
}

运行结果

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

分析

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

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

虚拟继承

虚基类的两种实现方式编译器引入所谓的 base class table,每一个类如果有一个或多个虚基类,就会有编译器安插一个指针,指向虚基类表,而真正的虚基类指针放在表格中。

虚基类的声明_虚基类和抽象类的区别_虚基类

2.第二种方法是在 table中放置 base class的

虚基类的声明_虚基类和抽象类的区别_虚基类

虚函数表到底在哪

因为在程序里每个类只需要一个vtbl拷贝虚基类,所以编译器肯定会遇到一个棘手的问题:

把它放在哪里。大多数程序和程序库由多个(目标)文件连接而成,但是每个

文件之间是独立的。哪个文件应该包含给定类的vtbl呢?你可能会认为放在包含

main函数的文件里,但是程序库没有main,而且无论如何包含main的源文件不会

涉及很多需要vtbl的类。编译器如何知道它们被要求建立哪一个vtbl呢?

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

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

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注