获取结构体成员偏移量的方法

不要站着调试程序,那会使得你的耐心减半,你需要的是全神贯注。—— Dave Storer

前言

记录工作中学习到的知识,在这里做些笔记,方便自己后面经常温习。


正文

为什么要获取结构体成员变量的偏移

我在做嵌入式工作时,在已知要获取信息的 flash 地址时,需要取出相对应的信息元素,这个时候时常需要知道结构体相对于已知地址的偏移,方便快捷的从 flash 中取出信息元素,所以时常使用这个方式。


方法

我们先用普通的方法获取结构体偏移,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>

typedef struct {
int a;
int b;
int c;
}x_t;

void test(void)
{
x_t p;
printf("p_addr=%d\r\n", (int)&p);
printf("p.a_addr=%d\r\n", (int)&p.a);
printf("p.b_addr=%d\r\n", (int)&p.b);
printf("p.c_addr=%d\r\n", (int)&p.c);

printf("a_offset=%d\r\n", (int)&(p.a)-(int)&p);
printf("b_offset=%d\r\n", (int)&(p.b)-(int)&p);
printf("c_offset=%d\r\n", (int)&(p.c)-(int)&p);
}

int main(void)
{
test();
return 0;
}

输出结果:

可以看出,如果要获取结构体成员变量相对于结构体的偏移,则需要先获取结构体地址,再获取成员变量地址,成员变量地址减去结构体地址,才能获取相应的偏移。

我们再用另一种方式获取结构体偏移,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>

typedef struct {
int a;
int b;
int c;
}x_t;

void test1(void)
{
x_t * p = 0;
printf("a_offset = %d\n", (int)(&(p->a)));
printf("b_offset = %d\n", (int)(&(p->b)));
printf("c_offset = %d\n", (int)(&(p->c)));
}


int main(void)
{
test1();
return 0;
}

输出结果:

这里先把结构体地址指向地址 0x00000000,这时候获取成员变量相对于结构体的偏移就轻松多了,减少了一步操作,减少了计算量。


linux内核代码求结构体成员变量偏移的方法

在内核代码 ./include/linux/stddef.h 文件中有如下定义:

1
2
3
4
#ifndef __CHECKER__
#undef offsetof
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#endif

我们参考代码编写后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>

typedef struct
{
int a;
int b;
int c;
}x_t;


#define offsetof(TYPE, MEMBER) ((size_t) &(((TYPE*)0)->MEMBER))

void test2()
{
printf("a_offset = %d\n", (int)&((x_t*)0)->a);
printf("b_offset = %d\n", (int)&((x_t*)0)->b);
printf("c_offset = %d\n", (int)&((x_t*)0)->c);
}

void test3()
{
printf("a_offset = %d\n", offsetof(x_t, a));
printf("b_offset = %d\n", offsetof(x_t, b));
printf("c_offset = %d\n", offsetof(x_t, c));
}

int main(void)
{
printf("test2():\r\n");
test2();
printf("test3():\r\n");
test3();
return 0;
}

输出结果:

test2 和 test3 与上边的 test1 方法其实是一样的,宏定义中的(TYPE*)0是一个空指针,如果使用空指针访问成员肯定造成段错误,但是前面的 “&” 这个符号,表示我们仅仅取 MEMBER 字段的地址,而不是引用该字段内容,因此不会造成段错误。通过将结构体地址指向 0x0 来获得结构体成员变量相对结构体地址,即方便我们使用,也方便理解。


结尾

正如前言说的,记录工作中积累的点滴经验,怕自己因为少用忘了,在这里做个记录,方便回顾。