作者:阿波
链接:http://blog.csdn.net/livelylittlefish/article/details/20565261
(几年前的一篇文章,翻出来共享一下。)
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多好的结构和算法,笔者以后慢慢讲述。
遇到问题要思考,且是深入思考。学习内核要学习其设计思想和方法,记录是要整理自己的思路,以备后忘。
去年和最近面试过很多人,问到这个题目,但极少人能答上来,这种问题可是基本功啊。还不知道如何获取结构体成员偏移量的同学,敲打键盘,试一下吧。:)