标题C语言offsetof用法以及其扩展用法
offsetof由于不是标准库的函数,所以得查一下,在stddef.h中,搜索一下编译器的这个头文件位置:
暴力一点,直接在根目录下搜索,find -name “stddef.h”,找到了这个
/usr/lib/gcc/i586-suse-linux/4.8/include/stddef.h
打开看下文件内容,找到offsetof
/* Offset of member MEMBER in a struct of type TYPE. */
#define offsetof(TYPE, MEMBER) __builtin_offsetof (TYPE, MEMBER)
这是目前GCC编译器的定义宏,在传统实现依赖于编译器多指针不是很挑剔,把结构体当做0地址处理,然后获取成员变量的偏移,如下:
#define offsetof(st, m) ((size_t)&(((st *)0)->m))
这个定义在C11标准下是非法的,0表示空,这里对空指针进行解引用,显然是有问题的。Linux用户老实用gcc的实现方式就ok。
Gcc的头文件注释就是针对结构体,查询该结构体的某个成员变量相对结构体偏移量,就是该成员前面还有多少字节的其他成员存储的内容。写个代码测试一下:
#include <stdio.h>
#include <stddef.h>
typedef struct Test
{
int a;
int b;
char c;
short d;
float e;
}test;
int main(int argc, char *argv[])
{
printf("offset of b = %d\n", offsetof(test, b));
printf("offset of c = %d\n", offsetof(test, c));
printf("offset of d = %d\n", offsetof(test, d));
printf("offset of e = %d\n", offsetof(test, e));
return 0;
}
编译执行结果如下:
说到这里可能都觉得,这个有啥用,我都知道这个结构体的成员变量了,我自己算出来不就完事了?
其实在应用层的开发中好像比较少用到这个东西,但是在linux内核开发和驱动开发中比较常用这个东西来通过某个成员变量获取结构体的首地址,来实现一些其他的操作。比如Linux内核使用offsetof()来实现container_of()
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
这个宏经常被用来通过结构体某个内嵌成员变量来获得该成员变量外包结构体的指针,好利用该结构体指针做其他用途。
举个栗子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
typedef struct Test
{
int a;
int b;
char c;
short d;
float e;
}test;
int main(int argc, char *argv[])
{
/*
printf("offset of b = %d\n", offsetof(test, b));
printf("offset of c = %d\n", offsetof(test, c));
printf("offset of d = %d\n", offsetof(test, d));
printf("offset of e = %d\n", offsetof(test, e));
*/
test *my_test = NULL;
my_test = (test *)malloc(sizeof(test));
if(!my_test)
{
return -1;
}
memset(my_test, 0, sizeof(test));
my_test->a = 1;
my_test->b = 2;
my_test->c = 3;
my_test->d = 4;
my_test->e = 5;
int *ptr = &my_test->b;
test *ret = container_of(ptr, test, b);
printf("ret->b = %d\n", ret->b);
free(my_test);
my_test = NULL;
return 0;
}
编译运行:
root@LYX:~/share/test/c_offect# gcc offset.c
root@LYX:~/share/test/c_offect# ./a.out
ret->b = 2
细心的同学可能会发现,我的运行环境变了。没关系,这个一部分是在公司的休息时间写的,一部分是在家的pc上写的,所以会有环境的细微变化,不影响。
通过上面这个例子,是不是就可以解决只知道某个结构体的成员变量,然后通过这个成员变量找到该结构体的指针,这一用法其实在内核中很常见,如一些链表的操作。
说到这里我突然想到一个问题:一个单链表,先有一个指针指向其中一个节点,怎么获得他的上一个节点,或者有个指针指向该节点的某个成员变量,求上一个节点,是不是可以用这种方法去做,这里不展开去讲了,改天有时间写个代码出来实操一遍。