從Linux核心程式碼中學習獲得結構體成員偏移量的方法
阿新 • • 發佈:2019-02-14
作者:阿波
(幾年前的一篇文章,翻出來共享一下。)
Content
0. 引子
1.舉例
(1) 程式碼
(2) 檢查結果
(3) 為什麼從0開始?
(4) 從非0地址開始的結果
2.小結
0. 引子
在linux-2.26.23版的核心程式碼中,./include/linux/stddef.h檔案中有如下定義。
00020: #undef offsetof 00021: #ifdef compiler_offsetof 00022: #define offsetof(TYPE,MEMBER) compiler_offsetof(TYPE,MEMBER) 00023: #else 00024: #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) 00025: #endif 00026: #endif /* KERNEL */ 00027: 00028: #endif
compiler_offsetof不是筆者關心的內容,第二個巨集定義即為本文討論的方法。
這個巨集定義很好解釋:將0地址強制轉換為TYPE型別指標,並取得MEMBER,然後獲取該MEMBER地址,最後將該地址強制轉換為表示大小的整數。該整數即為該成員的偏移量。
1.舉例
(1) 程式碼
00001: / ** 00002: * test the offset of a member of a struct 00003: */ 00004: 00005: #include <stdio.h> 00006: 00007: typedef struct 00008: { 00009: int x; 00010: int y; 00011: int z; 00012: } Point; 00013: 00014: //#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE*)0)- >MEMBER) 00015: #define offsetof(TYPE, MEMBER) ((size_t) &(((TYPE*)0)- >MEMBER)) 00016: 00017: void test1() 00018: { 00019: int x = (size_t) &((Point*)0)- >x; 00020: int y = (size_t) &((Point*)0)- >y; 00021: int z = (size_t) &(((Point*)0)- >z); 00022: printf("offset: x = %d, y = %d, z = %d\n", x, y, z); 00023: } 00024: 00025: void test2() 00026: { 00027: printf("Point.x offset = %d\n", offsetof(Point, x)); 00028: printf("Point.y offset = %d\n", offsetof(Point, y)); 00029: printf("Point.z offset = %d\n", offsetof(Point, z)); 00030: } 00031: 00032: int main(int argc, char** argv) 00033: { 00034: test1(); 00035: printf("\n"); 00036: test2(); 00037: return 0; 00038: } 00039:
在該例子中,筆者借用Linux核心的方法,兩種方式實現
(2) 檢查結果
# ./offsetof
offset: x = 0, y = 4, z = 8
Point.x offset = 0
Point.y offset = 4
Point.z offset = 8
無需說明,這個結果是正確的。
(3) 為什麼從0開始?
從第1節敘述的該方法的思想中,可以看出,從0開始的目的是不需要在獲得某個成員的地址後減去結構體的起始地址。
下面我們看看從非0開始的結果。
(4) 從非0地址開始的結果
修改test1()函式,如下。
結果如下。00017: void test1() 00018: { 00019: int x = (size_t) &((Point*)1000)- >x; 00020: int y = (size_t) &((Point*)1000)- >y; 00021: int z = (size_t) &(((Point*)1000)- >z); 00022: printf("offset: x = %d, y = %d, z = %d\n", x, y, z); 00023: }
# ./offsetof
offset: x = 1000, y = 1004, z = 1008
Point.x offset = 0
Point.y offset = 4
Point.z offset = 8
可以看出,如果從非0開始,實際上獲得的是該成員的實際地址(當然,該實際地址是相對於給定的起始地址來說的,並非真實的記憶體地址)。該實際地址減去給定的起始地址後也可得成員的偏移量。
2.小結
本文借用Linux核心的方法,舉例敘述了獲取結構體成員偏移量的方法。Linux核心中還有N多好的結構和演算法,筆者以後慢慢講述。
遇到問題要思考,且是深入思考。學習核心要學習其設計思想和方法,記錄是要整理自己的思路,以備後忘。
去年和最近面試過很多人,問到這個題目,但極少人能答上來,這種問題可是基本功啊。還不知道如何獲取結構體成員偏移量的同學,敲打鍵盤,試一下吧。:)