一、#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE*)0)->MEMBER) (include/linux/stddef.h)linux
1.1 功能:ide
返回結構體TYPE中MEMBER成員相對於結構體首地址的偏移量,以字節爲單位。函數
1.2 解析:指針
此類複雜表達式的解析應該採用從內向外、逐層理解的方式。ip
首先,(TYPE *)0表示將數字0強制類型轉換爲TYPE類型(TYPE爲結構體類型)的指針。所以這裏的0表明內存地址0,即咱們認爲內存地址0開始的sizeof(TYPE)個字節內存儲的是一個TYPE類型的變量。內存
而後,((TYPE *)0)->MEMBER 獲得該結構體變量中的MEMBER成員變量,get
而 &(((TYPE*)0)->MEMBER) 使用取地址符&取得了MEMBER成員變量的地址,(size_t)加在前面表示將MEMBER成員變量的地址強制類型轉換爲size_t(即unsigned int),並將結果做爲宏的返回值。編譯器
可見,offsetof宏返回的是MEMBER成員在內存中的實際地址。又由於整個結構體的起始地址是0,所以MEMBER成員的實際地址在數值上就等於MEMBER成員相對於結構體首地址的偏移量。io
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以前的成員,而後再次彙編,對比彙編後代碼的不一樣,以此來驗證咱們的推論。