您的位置:首页 > 其它

结构体之offsetof宏详细解析

2016-05-11 15:55 393 查看
1、#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE*)0)->MEMBER) (include/linux/stddef.h)

1.1 功能:

返回结构体TYPE中MEMBER成员相对于结构体首地址的偏移量,以字节为单位。

1.2 解析:

此类复杂表达式的解析应该采用从内向外、逐层理解的方式。

首先,(TYPE *)0表示将数字0强制类型转换为TYPE类型(TYPE为结构体类型)的指针。因此这里的0代表内存地址0,即我们认为内存地址0开始的sizeof(TYPE)个字节内存储的是一个TYPE类型的变量。

然后,((TYPE *)0)->MEMBER 得到该结构体变量中的MEMBER成员变量,

而 &(((TYPE*)0)->MEMBER) 使用取地址符&取得了MEMBER成员变量的地址,(size_t)加在前面表示将MEMBER成员变量的地址强制类型转换为size_t(即unsigned int),并将结果作为宏的返回值。

可见,offsetof宏返回的是MEMBER成员在内存中的实际地址。又因为整个结构体的起始地址是0,因此MEMBER成员的实际地址在数值上就等于MEMBER成员相对于结构体首地址的偏移量。

1.3 扩展思考:

1.3.1 使用offsetof宏会影响内存0地址处的值吗?

答案是不会,从1.3.2可知offsetof宏的运算是在C编译器编译时完成的,因此内存的0地址在机器指令中根本未被操作,当然不会影响其值了。

1.3.2offsetof宏返回的MEMBER相对于结构体首地址的偏移量是如何得到的?->符号如何能正确寻址到结构体中某个成员变量?

想探究struct如何通过->精确寻址每一个成员,最好的办法就是将C代码汇编为.S的汇编语言代码,通过观察汇编代码可以看到C编译器对代码处理的具体细节。我们的示例代码如下:

#include"stdio.h"

#definemyoffsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

typedefstruct st

{

int a;

int c; //将该行加上或去掉,对比得到的汇编代码的差别

short d; //将该行加上或去掉,对比得到的汇编代码的差别

char b;

}st;

intgetoffsetof(void)

{

return myoffsetof(struct st, b);

}

将以上代码保存为offsetof.c,并且使用arm-linux-gcc offsetof.c –S执行汇编,则会得到offsetof.s文件,内容如下:

.file "offsetof.c"

.text

.align 2

.global getoffsetof

.type getoffsetof, %function

getoffsetof:

@ Function supports interworking.

@ args = 0, pretend = 0, frame = 0

@ frame_needed = 1, uses_anonymous_args= 0

mov ip, sp // 这三行

stmfd sp!, {fp, ip, lr, pc} // 是函数

sub fp, ip, #4 // 栈帧保存

mov r3, #10 // #10即是offsetof宏计算得到的值

mov r0, r3 // 将返回值置于R0中

sub sp, fp, #12 // 函数栈帧

ldmfd sp, {fp, sp, lr} // 恢复

bx lr // 函数返回

.size getoffsetof, .-getoffsetof

.ident "GCC: (GNU) 4.1.2"

以上汇编代码中mov r3, #10一句可以看出,offsetof宏计算member的偏移量是C编译器在编译阶段完成的,而并不需要CPU在运行时去计算得出。

可以尝试着更改struct st中成员b之前的成员,然后再次汇编,对比汇编后代码的不同,以此来验证我们的推论。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: