在C++中,结构体和类都具有构造函数、析构函数和成员函数,两者只有一个区別:结构体的访问控制默认为public,而类的默认访问控制是private。对于C++中的结构体而言, public, private、protected的访问控制都是在编译期进行检査,当越权访问时,编译过程中会检査出此类错误并给予提示。编译成功后,程序在执行的过程中不会在访问控制方面做任 何检査和限制。因此,在反汇编中,C++中的结构体与类没有分别,两者的原理相同,只是类型名称不同,本章使用的示例多为类。
#对象的内存布局
结构体和类都是抽象的,在真实世界中它们只可以表示某个群体,无法确定这个群体中的某个独立个体,而对象这是群体中独立存在的个体。例如,地球上最智慧的群体生物是人,人便是抽象事物,可以看做是一个类。“人”只能描述这个类型的事物具有哪些特征, 而无法得知具体是哪一个人。而在“人”这个类中,如关羽、张飞等都是独立存在的实体, 可被看做是“人”这个类中的实体对象。
由于类是抽象概念.当两个类的特征相同时,它们之间应该是相等的关系。而对象是实际存在的,即使它们之间所包含的数据相同.也不能视为同一个对象,这就如同人类中的两个实体对象,即使他们是一对双胞胎,也不能因为他们的相貌等各方面的特征都相同就将他们描述成同一个人。下面我们将通过一个简单的示例来加深理解类与对 象之间的关系。
class CNumber
{
public:
CNumber()
{
m_nOne = 1;
m_nTwo = 2;
}
int GetNumberOne()
{
return m_nOne;
}
int GetNumberTwo()
{
return m_nTwo;
}
private:
int m_nOne;
int m_nTwo;
};
int main()
{
CNumber Number;
}
上述代码中定义了自定义类型CNumber类,以及该类的实例对象Number。CNumber 类型与C++中提供的 int 都属于数据类型。在32位下,整型变量的数据大小为4字节。使用 class关键字的自定义类型如何分配各数据成员呢?我们下面通过 gdbserver
来调试运行代码,以分析对象Number的各成员在内存中的布局,如图所示。
000091fc <main>:
91fc: e92d4800 push {fp, lr}
9200: e28db004 add fp, sp, #4
9204: e24dd008 sub sp, sp, #8
9208: e24b300c sub r3, fp, #12
920c: e1a00003 mov r0, r3
9210: eb000022 bl 92a0 <_ZN7CNumberC1Ev>
9214: e3a03000 mov r3, #0
9218: e1a00003 mov r0, r3
921c: e24bd004 sub sp, fp, #4
9220: e8bd8800 pop {fp, pc}
000092a0 <_ZN7CNumberC1Ev>:
92a0: e52db004 push {fp} ; (str fp, [sp, #-4]!)
92a4: e28db000 add fp, sp, #0
92a8: e24dd00c sub sp, sp, #12
92ac: e50b0008 str r0, [fp, #-8]
92b0: e51b3008 ldr r3, [fp, #-8]
92b4: e3a02001 mov r2, #1
92b8: e5832000 str r2, [r3]
92bc: e51b3008 ldr r3, [fp, #-8]
92c0: e3a02002 mov r2, #2
92c4: e5832004 str r2, [r3, #4]
92c8: e51b3008 ldr r3, [fp, #-8]
92cc: e1a00003 mov r0, r3
92d0: e24bd000 sub sp, fp, #0
92d4: e49db004 pop {fp} ; (ldr fp, [sp], #4)
92d8: e12fff1e bx lr
在上图中,对象Number内存中的地址为 0xbefffd60 ,该地址处定义了对象 Number 的各个数据成员,它们分别存放在地址0xbefffd60与0xbefffd64处,对象 Number 中先定义的数据成员在低地址处,后定义的数据成员在髙地址处,依次排列(这个原则对于arm,x86,win,linux都成立)。对象的大小只包含数据成员,类成员函数属于执行代码,不属于类对象的数据。
根据上图可知,凡是属于CNumber类型的变量,在内存中都会占据8字节的空间。这8字节由类中的两个数据成员组成,它们都是int类型,各自的数据长度为4字节。从内存布局上看,类与数组非常相似,都是由多个数据元素构成,但类的能力要远远大于数组。类成员的数据类型定义非常广,除本身的对象外,任何已知数据类型都可以在类中定义。
为什么在类中不能定义自身的对象呢?因为类需要在申请内存的过程中计算出自身的实际大小。以用于实例化。如果在类中定义了自身的对象,在计算各数据成员的长度时,又会回到自身,这样就形成了递归定义,而这个递归并没有出口,是一个无限的循环递归定义,所以不能定义自身对象作为类成员。但是,自身类型的指针除外,因为任何类型的指针在32 位下所占用的内存大小始终为4字节,等同于一个常量值,因此将其作为类的数据成员不会影响长度的计算。根据以上知识,可以总结出如下的对象长度计算公式:
对象长度= sizeof(数据成员1) + sizeof (数据成员2) + ... ... + sizeof(数据成员n)