- 1.对象的内存布局
- 2.this指针
- 3.静态数据成员
- 4.对象作为函数参数
- 5.对象作为返回值
一般计算公式: 对象内存大小 = sizeof(数据成员1)+ sizeof(数据成员2) +. … + sizeof(数据成员n) 若类中没有继承和虚函数的定义,还有三种特殊情况考虑:空类、内存对齐、静态成员函数。 空类:空类的长度为1字节,如果不占用字节的话,this指针会悬空。考虑到类可以仅有成员函数,没有数据成员。 内存对齐:在VC中,类和结构体中的数据成员是根据在类或结构中出现的顺序来依次申请空间的,由于内存对齐原因,可能不会连续的排列,数据成员之间可能有间隙。 静态变量:与静态全局变量类似,存在的位置和全局变量一致,只是编译器增加了作用域的检查,作用域之外不可见。访问对象中的数据成员时,一般采用寄存器间接相对寻址,表达式esp±n±offset,或ebp±n±offset(其中esp±n或ebp±n是对象的首地址,offset 为数据成员相对对象首地址的偏移)。由于n和offset在编译阶段时属于常量,采用编译器优化时,表达式可能简化为 ebp±n 或 esp±n。 局部变量:ebp±n 或 esp±n。 局部变量分配并赋值,函 数的“{”被认为是分配局部变量空间的时机。在汇编层面局部变量分配体现为堆栈中以 EBP 寄存器为基址向低地址端分配的一个连续区域,通过 EBP 寄存器的相对寻址方式来寻址函数内的局部变量。由于堆栈增长的方向是高地址端到低地址端,因此函数中先定义的局部变量地址较大,后定义的变量地址逐渐变小,相邻定义的变量其地址一定相邻。由于全局数据和局部数据定义在不用的数据区而并不与局部变量相邻,根据程序局部性原理,相邻的数据会被缓存,因此对相同的运算,局部变量作为操作数的运算效率就可能高于有全局变量参与的运算。同时,局部变量分配和回收只需要移动堆栈指针ESP,因此效率最高。 寻址函数的参数 参数存放在以 EBP 为基址的高地址端。对参数的访问同样是通过EBP 寄存器相对寻址操作来实现。 返回值 return 语句将返回值返回到主调函数。在底层,参数是通过 EAX 寄存器或 EDX 寄存器传递给主调函数。 返回主调函数 函数的“}”被解释为函数体已经执行完。遇到“}”时,会将堆栈中的局部变量、程序中压入堆栈的寄存器的值全部弹出,将之前 CALL指令执行时压入堆栈的函数返回地址弹到指令指针寄存器 EIP,从而返回到主调函数。 堆栈平衡 堆栈平衡指的是将函数调用前压入堆栈的参数弹出堆栈,使堆栈恢复到其调用前的状态[3]。由于函数调用完成后,参数就是无用的数据了,因此需要将其移出堆栈。在 C语言中不需要进行堆栈平衡。而在汇编层面上却根据调用约定来确定由主调函数或是被调函数完成堆栈平衡。
2.this指针this指针是保存所属对象的首地址。在调用成员函数时,遵循默认的thiscall约定,利用寄存器ECX保存对象的首地址(即this指针),以寄存器传参的方式传递到成员函数中,即是this指针的约定。成员函数中访问成员数据既是通过this指针间接访问的。 thiscall并不属于关键字,是C++成员函数特有的调用方式。 thiscall的参数压栈顺序也是从右至左。 thiscall的栈平衡方式与_stdcall相同,由被调用方平衡。 并不是所有的this指针的传递都是通过寄存器ECX,可以强制改用其他调用方式(_如stdcall)
3.静态数据成员静态数据成员和静态变量的原理相同,因此静态数据成员的初值会被写入编译链接后的执行文件中。当程序被加载时,操作系统将执行文件中的数据读到对应的内存单元中,静态数据成员便已经存在,而这时类并没有实例对象。静态数据成员不属于某一个对象,与对象之间是多对一的关系。静态数据成员仅仅和类相关,和对象无关。所以在计算某个类的对象所占用内存的大小,静态数据成员是不计算在内的。
区别: 静态数据成员是常量地址,而普通数据成员一般存储在栈空间。 静态成员通过立即数间接寻址访问,而普通数据成员一般通过寄存器相对间接寻址访问。 静态成员访问时不需要this指针,而普通数据成员访问时需要使用this指针。
4.对象作为函数参数对象作为函数参数,编译器会把对象视为由几个基本类型的数据的组合,和多个参数函数传参类似。类对象中的数据成员的传参顺序为:最先定义的数据成员最后压栈,最后定义的数据成员最先压栈。当类有构造函数和析构函数,过程会更复杂一些。由于对象在向函数传递过程中,由于复制了对象,等同于又定义了一个对象,会调用复制构造函数。在函数退出时,复制的对象作为函数内部的局部变量被销毁,会调用析构函数。
5.对象作为返回值对象作为函数返回值,与基本类型不同。基本数据类型(双精度浮点数以及非标准的_int64类型除外)作为返回值时,通过寄存器EAX传递。对象作为返回值时,首先在调用函数中申请返回对象使用的栈空间,然后将返回对象的首地址作为参数,通过寄存器EAX传递给被调用函数。在退出被调用函数时,将返回对象中的数据复制到调用函数开辟的返回对象的栈空间,把返回对象的首地址通过寄存器EAX返回。返回的对象是临时存在的,也就是C++临时对象,作用域仅仅限于单条语句。