复合语句表达式的高级用法

什么是复合语句表达式?怎样依据结构体成员变量的地址来获取结构体变量的地址?这便是本文要解决的问题。

认识复合语句表达式

我在《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

上一篇:QML之全局变量


下一篇:个人项目所遇到的问题