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之前的成員,然后再次匯編,對比匯編后代碼的不同,以此來驗證我們的推論。