9.6 本章小结
本章首先讨论了结构体和类的内存结构,然后讨论了函数间对象传递的相关问题,以及这些问题在编译器内部的实现原理。我们看到,当对象结构简单、体积小时,函数间的对象传递直接使用eax和edx保存对象中的内容。当对象体积过大,结构复杂时,寄存器就明显不够用了,于是编译器在开发人员不知情的情况下,偷偷地给函数加上一个参数,将其作为返回值。传递参数对象时,存在一次复制过程,简单的对象直接按成员顺序push传参,复杂的对象则使用重复前缀的串操作指令rep movs,其edi被设置为栈顶。
在访问对象成员时,其寻址方式颇为特别,使用的是寄存器相对间接访问方式。这种访问方式可以作为识别对象的必要条件,但是还需考察成员类型。如果类型一致,则应优先考虑是数组的访问,因为在数组的下标访问时,编译器也可能采用寄存器相对间接访问方式,如a[i],当i为常量时就会出现寄存器相对间接访问方式。当对象在栈内时,其首地址表示为ebp±n或者esp+n,其中n为立即数,而编译器计算对象成员的地址为对象首地址+成员偏移量,这个偏移量值是编译器在编译过程中确定的,视为常量值,联合上式,对象成员的地址表达为ebp±n+offset或者esp+n+offset,其中n和offset(成员偏移量)皆为常量,符合常量折叠的优化条件,于是在编译时可计算出N=n±offset,所以在分析的时候,我们只能看到ebp±N或者esp+N。
思考题答案:
&((struct A*)NULL)->m_float不会崩溃,这时求m_float的地址,根据前面提出的结构体寻址公式:
p->member的地址=指针p的地址值+member在type中的偏移量
代入得:
&((struct A*)NULL)->m_float=0+4=4,这个表达式实际上是求结构体内成员的偏移量。
可以定义如下宏,用于在不产生对象的情况下取得成员偏移量:
#define offsetof(s, m)(size_t)&(((s*)0)->m)
大家不用自行定义,在VC的stddef.h中有offsetof的官方定义。