C语言高级用法---typeof( ((type *)0)->member )和offset_of()

本文详细解析了C语言中typeof(((type*)0)->member)的含义及其应用场景,深入介绍了offsetof()宏的使用方法,通过实例展示了如何获取结构体成员的偏移地址。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

本文讲解typeof( ((type *)0)->member )的含义,并在此基础上学习offset_of()的用法。

typeof( ((type *)0)->member )

ANSI C标准允许值为0的常量被强制转换成任何一种类型的指针,并且转换的结果是个NULL,因此((type *)0)的结果就是一个类型为type *的NULL指针.

如果利用这个NULL指针来访问type的成员当然是非法的,但typeof( ((type *)0)->member )是想取该成员的类型(有关typeof()的用法可参考这里),所有编译器不会生成访问type成员的代码,类似的代码&( ((type *)0)->member )在最前面有个取地址符&,它的意图是想取member的地址,所以编译器同样会优化为直接取地址。

struct apple{
    int weight;
    int color;
};

int main(int argc, char *argv[])
{
    int weight = 5;

    typeof(((struct apple *)0)->weight) *ap1 = &weight;//定义一个指针变量ap1, ap1的类型为apple成员weight的类型。
    printf("ap1's value:%d\n", *ap1);
}

printf("%d\n", offset_of(struct apple, color));

[root@xxx c_base]# ./a.out
ap1's value:5

offset_of()

理解了以上内容后再看offset_of(),它的作用是获取结构体中某个成员相对于该结构体首元素地址的偏移量。

#define offset_of(type, memb) \
        ((unsigned long)(&((type *)0)->memb))

比如想知道sruct apple中的color是在结构体中的哪个位置,可以这么做:

printf("colore offset addr:%d\n", offset_of(struct apple, color));
[root@xxx c_base]# ./a.out
colore offset addr:4

如上结果表示colore成员在结构体apple中的偏移地址是4.

参考文章

  1. int( (LONG)(LONG_PTR)&(((type *)0)->field))的最好解释
  2. C 中&(((TYPE *)0)->member) 中指针为0 为什么不会报错?
<think>我们被要求解释container_of宏的实现原理。container_of宏在Linux内核中广泛使用,它用于通过一个结构体成员的指针来获取整个结构体的指针。 ### container_of宏的作用 假设我们有一个结构体类型`struct my_struct`,其中包含多个成员,例如: ```c struct my_struct { int some_data; char another_data; // ... 其他成员 }; ``` 如果我们有一个指向结构体成员(比如`another_data`)的指针,我们可以使用`container_of`宏来获取指向整个结构体(`struct my_struct`)的指针。 ### container_of宏的定义 在Linux内核中,`container_of`宏通常定义如下: ```c #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type, member) );}) ``` 或者更简洁的版本(但实际内核代码中使用了上述形式,包括类型检查偏移量计算): ```c #define container_of(ptr, type, member) \ ((type *)( (char *)(ptr) - offsetof(type, member) )) ``` 实际上,第一个定义中多了一个类型检查的步骤,我们分别解释。 ### 实现原理 1. **计算成员在结构体中的偏移量**:使用`offsetof`宏(或类似方法)计算成员`member`在结构体类型`type`中的偏移量(以字节为单位)。`offsetof`宏通常定义在`stddef.h`中,其实现通常依赖于编译器内置功能,例如: ```c #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) ``` 这个宏将地址0强制转换为`TYPE*`类型,然后取成员`MEMBER`的地址。由于结构体从地址0开始,所以成员`MEMBER`的地址就是它相对于结构体起始地址的偏移量。 2. **计算结构体的起始地址**:有了成员指针`ptr`偏移量,我们就可以通过将`ptr`(转换为`char*`,以便进行字节级别的指针运算)减去偏移量,得到结构体的起始地址。 3. **类型转换**:将计算得到的地址转换为`type*`类型。 ### 详细步骤 - 步骤1:`offsetof(type, member)`计算出成员`member`在结构体`type`中的偏移量(假设为`offset`)。 - 步骤2:将传入的成员指针`ptr`转换为`char*`类型(因为`char*`的加减运算是以字节为单位的)。 - 步骤3:用`(char *)ptr`减去`offset`,得到结构体起始地址(仍然是一个`char*`)。 - 步骤4:将这个地址转换为`(type *)`,即指向结构体`type`的指针。 ### 类型检查(扩展) 在第一个定义中,有一行用于类型检查: ```c const typeof( ((type *)0)->member ) *__mptr = (ptr); ``` 这行代码的作用是: - 使用`typeof`获取结构体成员`member`的类型。 - 声明一个指向该类型的指针`__mptr`,并用`ptr`初始化它。 - 这样,如果`ptr`的类型与`member`的类型不匹配,编译器会发出警告。 ### 示例 假设我们有以下结构体: ```c struct sample { int a; char b; double c; }; ``` 现在,我们有一个指向成员`b`的指针: ```c struct sample s; char *p = &s.b; ``` 我们可以通过`container_of`宏获取指向`s`的指针: ```c struct sample *s_ptr = container_of(p, struct sample, b); ``` 计算过程: 1. 计算`b`在`struct sample`中的偏移量:`offsetof(struct sample, b)`。由于`a`是`int`(假设4字节),则`b`的偏移量是4(字节)。 2. 将`p`(即`&s.b`)转换为`char*`,然后减去4,得到`s`的起始地址。 3. 将该地址转换为`struct sample*`。 ### 注意事项 - 该宏依赖于指针运算,因此必须确保指针运算正确。使用`char*`进行减法是因为`char`的大小为1字节,这样偏移量就是字节数。 - 在用户空间程序中,使用`container_of`需要包含`stddef.h`(为了`offsetof`),并自行定义`container_of`宏(因为Linux内核中的版本使用了GNU扩展,如`({ ... })`语句表达式,这在用户空间GCC编译器中可用,但可能不具可移植性)。 ### 引用说明 在Linux内核源代码中,`container_of`宏被广泛使用,其定义可以在`include/linux/kernel.h`或类似文件中找到[^1]。 §§相关问题§§ 1. `container_of`宏在哪些场景下使用? 2. 如何自己实现`offsetof`宏? 3. `container_of`宏在用户空间程序中使用需要注意什么? 4. 除了`container_of`宏,还有其他方法从成员指针获取结构体指针吗? [^1]: 参考Linux内核源码中的`include/linux/kernel.h`文件。</think>### container_of宏实现原理详解 `container_of`宏是Linux内核中广泛使用的关键宏,用于通过结构体成员的指针获取整个结构体的指针。它在链表、设备驱动等场景中应用广泛。 #### 1. 宏定义(标准实现) ```c #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type, member) );}) ``` #### 2. 实现原理分解 ##### (1) 类型安全检查 ```c const typeof( ((type *)0)->member ) *__mptr = (ptr); ``` - 使用`typeof`获取成员变量的类型 - 创建临时指针`__mptr`并赋值为传入的`ptr` - **作用**:验证`ptr`是否与`type.member`类型匹配(类型不匹配时触发编译警告) ##### (2) 偏移量计算 ```c offsetof(type, member) ``` - `offsetof`宏计算成员在结构体中的偏移量 - 典型实现:`#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)` - **原理**:将地址0强制转换为结构体指针,取成员地址即得偏移量 ##### (3) 指针运算 ```c (char *)__mptr - offsetof(type, member) ``` - 将成员指针转为`char*`(确保字节级计算) - 减去成员偏移量得到结构体起始地址 - 最后转换为`(type *)`类型指针 #### 3. 数学表达 设结构体起始地址为$S$,成员偏移量为$\delta$,成员指针为$P$,则有: $$ S = P - \delta $$ 其中$\delta = \text{offsetof}(type, member)$ #### 4. 工作示例 ```c struct person { int age; char name[20]; }; struct person john = {30, "John"}; char *name_ptr = john.name; // 成员指针 // 通过name_ptr获取person结构体指针 struct person *p = container_of(name_ptr, struct person, name); ``` 计算过程: 1. `offsetof(struct person, name) = 4`(假设int占4字节) 2. `(char *)name_ptr - 4` → 得到结构体起始地址 #### 5. 关键特性 - **类型安全**:通过`typeof`进行编译时类型检查 - **内存无关**:计算仅依赖类型信息,不访问实际内存 - **零指针技巧**:利用`((type *)0)->member`获取偏移量(未实际解引用空指针) #### 6. 应用场景 - Linux内核链表实现(`list_entry`宏) - 设备驱动中通过子设备获取父设备结构 - 任何需要从成员指针反向定位父结构的场景[^1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值