前言:
我对编写那些“零基础精通C ++”系列的胡扯没有兴趣。互联网上有很多C / C ++入门文章。令人眼花,乱,因此C / C ++基础不适合学生,我建议您先从“ C Primer”一书开始,然后再读两本书“ Conquering C Pointer”和“ Understanding C Pointer”,后两本书可以被交叉引用,目的是打下良好的基础,当您了解C指针时,您会发现您的C素养将达到“猿人高峰”。如果您使用的是最新版本的“ C ++ Primer”,那么您的大脑中将具有C / C ++指针和内存建模的特定轮廓。遵循我的主题的读者可能知道我的主题是基于C / C ++内存建模的,因为学习C / C ++内存建模对于阅读其他C / C ++源代码非常重要。
从本文开始,我们将继续深入研究C ++运行时动态调度的相关主题。到目前为止,我们已经验证了gdb不会使用简单类型类和默认构造函数创建虚拟表。在本文中,我们将解释非虚拟派生类的结构和内存布局。这是虚拟类和非虚拟类之间的正确区别。内存分配差异的前提。如果您对面向对象的C / C ++不太了解,请先移至此处。
C ++面向对象的作品简述

样品导入
让我介绍两个新的类。从Person继承的基类Person和派生类Student。请注意,两者都使用其方法who()的相同签名。
#include
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开始,该指令指示无法进行运行时决策。指针的类型在编译时是已知的,并且编译器将选择正确的who()方法调用,类似于callq 0x400626,这在编译时也是静态命令。下图说明了此类别中的所有内容:



但是我们也可以通过定义基类的名称空间来调用基类的方法,如下所示
m->Person::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) 我们用它绘制如下图


以小数开头的内存位置(在此示例中,将0x60201 0)分配给堆上的Student对象。它由主堆栈帧上的指针变量m指向(地址0x7fffffffe2e 8)依此类推,Person对象也位于堆上的区域是0x602030。众所周知,堆向上增长,而堆栈向下增长。堆中对象的组织和堆栈框架在很大程度上取决于编译器和操作系统使用了内存管理方法。
某些值可以完全优化,不需要将其压入堆栈,而直接由寄存器替换。但是,在此示例中,没有使用用于编译的优化选项,因此x86约定仍用于组织程序堆栈。
从上面的内存布局中,我们可以得出一些启示:
继承链中的构建顺序
这实际上是上一篇文章中讨论的RAII多重继承约定。从拆卸的角度,我们将加深对此过程的了解



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


继承的初始化过程
垃圾回收的过程与继承列表中定义的父类的顺序相反。
从汇编代码可以看出,在每个构造函数的汇编上下文中,在retq指令的执行返回之前,当前构造函数已将一些初始化的局部变量缓存到缓存中的内存地址所指向的位置。可用寄存器是的,当然,通常是rax寄存器。
摘要
请勿对派生类的成员进行任何更改,并首先初始化基类的构造函数。在引入虚拟表时,这是一个非常重要的概念,因为此序列定义了哪些功能在什么阶段可见。
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/shoujiruanjian/article-363559-1.html
演员杨洋加油