#ifndef offsetof #define offsetof(TYPE, MEMBER) ((size_t)&((TYPE*)0)->MEMBER) #endif #ifndef container_of #define container_of(ptr, type, member) ({ \ const typeof(((type*)0)->member)* __mptr = (ptr); \ (type*)((char*)__mptr - offsetof(type, member));}) #endif
offsetof 用於計算 TYPE 結構體中 MEMBER 成員的偏移位置
#ifndef offsetof #define offsetof(TYPE, MEMBER) ((size_t)&((TYPE*)0)->MEMBER) #endif
- 編譯器清楚的知道結構體成員的偏移位置
- 經過結構體變量首地址與偏移量定位成員變量
#include <stdio.h> #ifndef offsetof #define offsetof(TYPE, MEMBER) ((size_t)&((TYPE*)0)->MEMBER) #endif struct ST { int i; // 0 int j; // 4 char c; // 8 }; void func(struct ST *pst) { int *pi = &(pst->i); // (unsigned int)pst + 0 int *pj = &(pst->j); // (unsigned int)pst + 4 char *pc = &(pst->c); // (unsigned int)pst + 8 printf("pst = %p\n", pst); printf("pi = %p\n", pi); printf("pj = %p\n", pj); printf("pc = %p\n", pc); } int main() { struct ST s = {0}; func(&s); printf("---------\n"); func(NULL); printf("---------\n"); printf("offset i : %d\n", offsetof(struct ST, i)); printf("offset j : %d\n", offsetof(struct ST, j)); printf("offset c : %d\n", offsetof(struct ST, c)); return 0; }
計算成員變量與其結構體變量首地址的偏移量編程
- ({}) 是 GNU 編譯器的語法擴展
- ({}) 與逗號表達時相似,結果爲最後一個語句的值
#include <stdio.h> void method_1() { int a = 0; int b = 0; int r = ( a = 1, b = 2, a + b ); printf("r = %d\n", r); } void method_2() { int r = ({ int a = 1; int b = 2; a + b; }); printf("r = %d\n", r); } int main() { method_1(); method_2(); return 0; }
輸出:安全
r = 3 r = 3
- typeof 是 GUN C 編譯器的特有關鍵字
- typeof 只在編譯期生效,用於獲得變量的類型
#include <stdio.h> int main() { int i = 100; typeof(i) j = i; const typeof(j) *p = &j; printf("sizeof(j) = %d\n", sizeof(j)); printf("j = %d\n", j); printf("*p = %d\n", *p); return 0; }
輸出:測試
sizeof(j) = 4 j = 100 *p = 100
數學關係:spa
pc = p + offset
==>指針
size_t offset = offsetof(struct ST, c); struct ST *p = (struct ST*)((char*)pc - offset);
#include <stdio.h> #ifndef offsetof #define offsetof(TYPE, MEMBER) ((size_t)&((TYPE*)0)->MEMBER) #endif #ifndef container_of #define container_of(ptr, type, member) ({ \ const typeof(((type*)0)->member)* __mptr = (ptr); \ (type*)((char*)__mptr - offsetof(type, member));}) #endif struct ST { int i; int j; char c; }; int main() { struct ST s = {0}; char *pc = &s.c; struct ST *pst = container_of(pc, struct ST, c); printf("&s = %p\n", &s); printf("pst = %p\n", pst); return 0; }
輸出:code
&s = 0061FEB8 pst = 0061FEB8
根據成員變量地址推導結構體變量首地址blog
思考:
const typeof(((type*)0)->member)* __mptr = (ptr);
下面這一段代碼測試證實沒有這一行也能夠獲得正確的結果,那它到底有什麼做用呢?編譯器
#include <stdio.h> #ifndef offsetof #define offsetof(TYPE, MEMBER) ((size_t)&((TYPE*)0)->MEMBER) #endif #ifndef container_of #define container_of(ptr, type, member) ({ \ const typeof(((type*)0)->member)* __mptr = (ptr); \ (type*)((char*)__mptr - offsetof(type, member));}) #endif #ifndef container_of_new #define container_of_new(ptr, type, member) ((type*)((char*)ptr - offsetof(type, member))) #endif struct ST { int i; int j; char c; }; int main() { struct ST s = {0}; char *pc = &s.c; struct ST *pst = container_of_new(pc, struct ST, c); // 注意!!! printf("&s = %p\n", &s); printf("pst = %p\n", pst); return 0; }
輸出:數學
&s = 0061FEBC pst = 0061FEBC
答疑 1: 爲了可以提供微弱但很重要的類型安全檢查
由於 continer_of 只能用宏來實現,但宏是由預處理器處理,僅進行簡單的文本替換,不會進行任何的類型檢查。這就有可能致使在編寫代碼時,因爲疏忽傳遞了錯誤的類型指針而編譯器不發出任何警告。it
測試:
int main() { struct ST s = {0}; int e = 0; int *pe = &e; struct ST *pst = container_of_new(pe, struct ST, c); printf("&s = %p\n", &s); printf("pst = %p\n", pst); return 0; }
輸出:【編譯無錯誤,無警告,但運行結果錯誤】
&s = 0061FEBC pst = 0061FEB0
int main() { struct ST s = {0}; int e = 0; int *pe = &e; struct ST *pst = container_of(pe, struct ST, c); printf("&s = %p\n", &s); printf("pst = %p\n", pst); return 0; }
編譯輸出:
warning: initialization from incompatible pointer type [-Wincompatible-pointer-types] const typeof(((type*)0)->member)* __mptr = (ptr); \ ^
答疑 2: 爲何使用 ({})
當定義指針變量時,一行代碼再也沒法完成 continer_of 的總體功能(逗號表達式中不能夠定義變量),因而使用了 GNU 的擴展語法({})分多行定義局部變量。
答疑 3: 此行代碼是怎樣獲取成員變量的類型的呢?
typeof(((type*)0)->member)
- 編譯器清楚的知道結構體成員變量的偏移位置
- ({}) 與逗號表達式類型,結果爲最後一個語句的值
- typeof 只在編譯期生效,用於獲得變量的類型
- container_of 使用 ({}) 進行類型安全檢查
以上內容整理於狄泰軟件學院系列課程,請你們保護原創!