C语言C语言&嵌入式IT狗工作室

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

2019-11-13  本文已影响0人  铁甲万能狗

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

示例导入

让我引入两个新的类。一个基类Person和一个从Person继承的派生类Student。请注意,两者都使用其方法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的约定组织程序栈。

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

继承链中的构造顺序

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


派生类的初始化过程

  1. 在当前派生类构造函数的上下文的按照继承列表中的顺序执行依次初始化继承链中各个父类的构造函数,本示例如下步骤。
  1. 返回派生类本身的构造函数执行剩余的指令集。
    继承的初始化过程

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

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

小结

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

上一篇 下一篇

猜你喜欢

热点阅读