第9篇:C++ 继承链的构造过程和内存布局

在本文中,我们将继续深入研究C ++运行时动态调度的相关话题。 到目前为止,我们已经验证gdb不会Trivial类型的类和默认构造函数创建虚拟表,我们在本篇将阐述非虚拟派生类它们的构造和内存布局,这是正确区分虚拟类和非虚拟类在内存分配方面的差异的前提。

示例导入

让我引入两个新的类。一个基类Person和一个从Person继承的派生类Student。请注意,两者都使用其方法who()的相同签名。

  • 有了动态调度后,将查询vtable并调用适当的方法。
  • 如果没有动态分配,则将调用与对象的指针类型匹配的方法。 Student *m将调用Student:: who(),而Person *p将调用Person::who()。让我们用汇编代码验证一下。
#include <stdio.h>

class Person {
public:
    Person() {}
    void who() {
        printf("I am a human!!\n");
    }

};

class Student: public Person{
public:
    Student() {}
    void career() {
        printf("I am a student!!\n");
    }
};

int main() {
    Student *m = new Student();
    m->who();

    Person *p = m;
    p->who();
}

我们看到who()方法已经在编译时已经植入Student类的上下文,从callq 0400658 <_ZN7Student3whoEv>,这条指令表明运行时决策是不可能的。 指针的类型在编译时是已知的,编译器会选择正确的who()方法调用,同理callq 0x400626 <_ZN6Person3whoEv>,也是一条编译时的静态指令。下图说明类一切:


但是我们也可以通过定义基类的名称空间来调用基类的方法,如下所示

m->Person::who()

内存布局

为了降低问题的复杂性,我将使用此代码的一些细微变化,去掉一些who方法:

class Person {
public:
    int age=6;
    Person() {}
};

class Student: public Person{
public:
    int idNo=1000;
    Student() {}
};

为了了解虚拟表的隐藏位置,让我们首先检查一下简单继承层次结构的内存布局。 让我们向Student和Person类添加一些整数变量。 使用此特定编译器的特定计算机上的sizeof(int)为4个字节。 但总是要记住,这个数字在不同硬件上尺寸可能不一样。我们在gdb中使用print命令输出代码中相关变量的地址,如下所示。

(gdb) p &m
$2 = (Student **) 0x7fffffffe2e8
(gdb) p &p
$3 = (Person **) 0x7fffffffe2e0
(gdb) p *m
$4 = {<Person> = {age = 6}, idNo = 1000}
(gdb) p *p
$5 = {age = 6}
(gdb) p m
$6 = (Student *) 0x602010
(gdb) p p
$7 = (Person *) 0x602030
(gdb) 

我们用绘制成如下图


以低数字开头的内存位置(在本例中为0x602010)是在堆上分配给Student对象的。由main栈帧上的指针变量m(地址0x7fffffffe2e8)指向它。如此类推Person对象也在堆上的区域是0x602030的位置。 众所周知,堆向上增长,而栈向下增长。 对象在堆和栈帧的的组织方式在很大程度上取决于所使用的编译器和操作系统的内存管理方式。

某些值可以完全优化并不需要入栈,直接并用寄存器代替。但本示例中没有使用任何编译的优化选项,因此仍然使用x86的约定组织程序栈。

从上面的内存布局中,我们可以得出几点启示:

  • 基类指针p和派生指针都分别类的第一个字节。基类之后是派生类,位于更高的地址处。这个简单示例有帮于我们找到虚拟指针。
  • 派生类的内存分配一般来说会比父类的内存分配要大,因为派生类会从基类中继承(拷贝)了public和protected修饰的数据成员的副本。在本例中Student对象得到了Person对象的age副本,当你尝试使用sizeof(*m)得到的结果是8,而Person的内存分配尺寸则是4.

继承链中的构造顺序

这其实是前面文章谈论到多继承的RAII约定,我们从反汇编的角度,加深对此过程的了解


派生类的初始化过程

  1. 在当前派生类构造函数的上下文的按照继承列表中的顺序执行依次初始化继承链中各个父类的构造函数,本示例如下步骤。
  • (1) 从main函数执行call 0x400640指令后,进入Student::Student()所在代码段中的上下文执行的一些入栈的操作(构造函数栈内存分配以及状态保存)。
  • (2) 执行call 0x44062c 即在Person类指令集所在的代码段地址初始化,在基类构造返回之前,汇编指令movl $0x6,(%rax),这个干了这些事:基类将数据成员-变量age=6作为返回值保存到rax寄存器中缓存的内存地址指向的位置(该位置在前一步的Student::Student构造函数的栈内存分配了,即-0x8(%rbp)的位置),以便派生类Student对象的构造函数读取作为它的数据成员。
  1. 返回派生类本身的构造函数执行剩余的指令集。
    继承的初始化过程

垃圾回收的过程 ,和继承列表中定义的父类顺序相反。

  • 首先,调用函数在结束之时隐式执行子类的解构函数。
  • 然后,依次逆序执行子类继承列表中父类的解构函数。

从汇编代码可知,在每个构造函数的的汇编上下文,在执行retq指令返回之前,当前的构造函数已经将初始化的一些局部变量缓存到可用的寄存器中缓存的内存地址所指向的位置了,当然通常是rax寄存器。

小结

不对派生类的成员进行任何更改而优先初始化基类的构造函数。 这对引入虚拟表时是一个非常重要的概念,因为此顺序定义了什么函数在什么阶段可见。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,692评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,482评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,995评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,223评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,245评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,208评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,091评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,929评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,346评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,570评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,739评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,437评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,037评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,677评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,833评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,760评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,647评论 2 354