什么是复合语句表达式?怎样依据结构体成员变量的地址来获取结构体变量的地址?这便是本文要解决的问题。
认识复合语句表达式
我在《C和指针》阅读笔记(4)一文中详细介绍了语句
、表达式
、操作符
之间的关系,在C语言中,语句是由表达式和(或)分号组成;表达式是由标识符或操作符组成。本文再深入一步,介绍一种更复杂一点儿的用法:复合语句作为表达式
,然后引出一个常用的编码技巧。
A compound statement enclosed in parentheses may appear as an expression in GNU C. This allows you to use loops, switches, and local variables within an expression.
在GNU C中,将复合语句通过小括号括起来后,可以当做表达式。这样的方式可使表达式完成更复杂的逻辑:如循环、分支、使用局部变量。
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
container_of宏的作用是 由结构体成员的地址来获取结构体的首地址
。暂时不关注宏的具体内容(先卖个关子),仅看其轮廓,可以发现#define container_of(ptr, type, member) ({})
,本文中将({})
称为复合语句表达式
。既然是表达式,那表达式的值又是什么呢?
/*
* ({statement1 statement2 ... statement3})
* ({expression1;expression2;...expressionN;})
*
* 整个复合语句表达式的值为expressionN的值
*/
所以,container_of 复合语句表达式的值 是 (type *)( (char *)__mptr - offsetof(type,member) );
的结果。
搞清楚了整体轮廓,接下来再讲解细节。
理解container_of
/*
/usr/lib/gcc/x86_64-linux-gnu/7/include/stddef.h:417
#define offsetof(TYPE, MEMBER) __builtin_offsetof (TYPE, MEMBER)
*/
// offsetof的本质逻辑如下:
//#define offsetof(type, member) (size_t)&(((type*)0)->member)
// 获取结构体成员在结构体中的偏移
// ((type*)0), 将0强转为指向type类型的指针
// &(((type*)0)->member), 获取成员member的地址;由于((type*)0)的地址为0, 那么member的地址即为其在结构体中的偏移
// 由结构体成员的地址来获取结构体的首地址
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
// ptr 表示结构体成员的地址
// type 表示结构体类型
// member 表示成员名, ptr和member代表相同成员
// typeof( ((type *)0)->member ) 获取member的数据类型
// typeof( ((type *)0)->member ) *__mptr 定义一个member数据类型的指针
// const typeof( ((type *)0)->member ) *__mptr = (ptr); 保存ptr地址
// ( (char *)__mptr - offsetof(type,member) ) ptr的地址 减去member的偏移量, 即得到结构体的地址
// (type *)( (char *)__mptr - offsetof(type,member) ) 再将地址强转为结构体指针
注意,如果使用g++ std=c++11编译,需要将typeof改为decltype
。当然,使用gcc编译时没有问题。
示例
#include<stdio.h>
#include<stdlib.h>
#include<stdint.h>
#include<malloc.h> // include offsetof(TYPE, MEMBER)
typedef struct
{
uint32_t a;
}com_queue_t;
typedef struct _sample_bgra_queue_s
{
com_queue_t *p_queue;
uint32_t load;
}sample_bgra_queue_t;
void test_offset()
{
size_t offset_p_queue = 0;
size_t offset_load = 0;
#if 0
offset_p_queue = offsetof(sample_bgra_queue_t,p_queue);
offset_load = offsetof(sample_bgra_queue_t,load);
#endif
offset_p_queue = (size_t)&(((sample_bgra_queue_t*)0)->p_queue);
offset_load = (size_t)&(((sample_bgra_queue_t*)0)->load);
printf("offset_p_queue is %lu\n",offset_p_queue);
printf("offset_load is %lu\n",offset_load);
}
int main(int argc, char** argv)
{
test_offset();
sample_bgra_queue_t * p_sbq = (sample_bgra_queue_t*)malloc(sizeof(sample_bgra_queue_t));
sample_bgra_queue_t * p_sbq2 = NULL;
sample_bgra_queue_t * p_sbq3 = NULL;
com_queue_t **pp_queue = &p_sbq->p_queue;
uint32_t *p_load = &p_sbq->load;
p_sbq2 = container_of(pp_queue, sample_bgra_queue_t, p_queue);
p_sbq3 = container_of(p_load, sample_bgra_queue_t, load);
printf("p_sbq addr is %p\n",p_sbq);
printf("p_sbq2 addr is %p\n",p_sbq2);
printf("p_sbq3 addr is %p\n",p_sbq3);
return 0;
}
参考:
https://gcc.gnu.org/onlinedocs/gcc-7.5.0/gcc/Statement-Exprs.html#Statement-Exprs