当前位置: 首页 > news >正文

C语言宏定义的底层应用

问题简介

最近在看开源 OCF 源码的时候,发现了一个 C 语言宏定义的常见使用技巧。从初见的不解,到了解之后的惊叹,不得不感叹于底层开发人员关于性能的极致追求!

在编程开发中,经常需要进行变量之间的类型转换操作。一个类A中存在类型为 B 的成员变量。现在我拥有一个类型为 B 的变量 v1,我需要得到一个类型为 A 的变量 v2,同时 v2->B 就为 变量 v1。

方法一

一般来说,对于此类问题,熟悉 C++ 的初学者会考虑采用以下的方法实现: 

// 用宏定义实现此功能!
#define CONVERT_B_TO_A(v1) A(v1)

class B {
    // B 类的定义
};
class A {
public:
    B b;
    A(const B& b_val) : b(b_val) {}
};
int main() {
    B v1;
    A v2 = CONVERT_B_TO_A(v1);  // 使用宏进行转换
    return 0;
}

 但是这种方法需要使用到 c++ 的构造函数,同时会产生额外的内存拷贝。

方法二

类似的,c 语言也可以采用这种方法实现,只需要模拟实现 c++ 中的构造函数即可。

代码如下:

// 假设 B 是一个简单的结构体
typedef struct {
    int value;
} B;
// 结构体 A 包含一个 B 类型的成员
typedef struct {
    int other_member;
    B b;
} A;
// 宏定义:将 B 类型的变量 v1 转换为 A 类型的变量 v2
#define CONVERT_B_TO_A(v1) \
    ({ \
        A temp; \
        memset(&temp, 0, sizeof(A)); \
        temp.b = v1; \
        temp; \
    })
int main() {
    B v1 = {42}; // 初始化 B 类型的变量 v1
    A v2 = CONVERT_B_TO_A(v1); // 使用宏将 v1 转换为 A 类型的变量 v2

    printf("v2.b.value = %d\n", v2.b.value); // 输出: v2.b.value = 42
    return 0;
}

c 语言的写法稍显繁琐,但是本质上和 c++ 完全相同。并且通过 c 语言的写法,我们能够更容易得发现这种方法的弊端,在宏定义中进行了一次多余的内存拷贝操作。

方法三

在底层开发中,这种功能性的宏定义会被调用得非常频繁,因此系统开发者们会追求极致的性能,性能最优的代码如下:

#define container_of(ptr, type, member) ({          \
    const typeof(((type *)0)->member)*__mptr = (ptr);    \
    (type *)((char *)__mptr - offsetof(type, member)); })

解析如下:

const typeof(((type *)0)->member)*__mptr = (ptr);

  • ((type *)0)

    • 将 0 强制转换为 type* 类型的指针。这里 0 是一个空指针,表示一个假设的 type 结构体的起始地址。

    • 这种写法不会真正访问内存,只是为了在编译时获取类型信息。

  • ((type *)0)->member

    • 访问假设的 type 结构体中的 member 成员。

    • 例如,如果 type 是 Amember 是 b,那么 ((A *)0)->b 就是 B 类型的成员。

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

    • 使用 typeof 关键字获取 member 成员的类型。

    • 例如,如果 member 是 B 类型,那么 typeof(((A *)0)->b) 就是 B

  • const typeof(((type *)0)->member)*__mptr = (ptr);

    • 定义一个指向 member 类型的指针 __mptr,并将其初始化为传入的 ptr

    • 这里使用 const 是为了防止意外修改 __mptr 指向的内容。

    • 例如,如果 ptr 是 B* 类型,那么 __mptr 也是 B* 类型。


2. (type *)((char *)__mptr - offsetof(type, member));

  • offsetof(type, member)

    • 使用 offsetof 宏计算 member 在 type 结构体中的偏移量。

    • offsetof 的实现通常是编译器内置的,它会返回 member 相对于结构体起始地址的字节偏移量。

    • 例如,如果 member 是 b,且 b 在 A 结构体中的偏移量是 4 字节,那么 offsetof(A, b) 返回 4

  • (char *)__mptr

    • 将 __mptr 强制转换为 char* 类型。char* 是一个字节指针,方便进行指针算术运算。

    • 因为指针算术的单位是指针指向的类型的大小,而 char 的大小是 1 字节,所以将指针转换为 char* 后,加减操作的单位就是字节。

  • (char *)__mptr - offsetof(type, member)

    • 将 __mptr 的地址减去 member 在结构体中的偏移量,得到结构体的起始地址。

    • 例如,如果 __mptr 指向 b,且 b 的偏移量是 4 字节,那么 (char *)__mptr - 4 就是 A 结构体的起始地址。

  • (type *)

    • 将计算出的地址强制转换为 type* 类型,即结构体的指针。

    • 例如,如果 type 是 A,那么最终返回的就是 A* 类型的指针。

总结

container_of 宏的原理可以概括为以下几步:

  1. 通过 typeof 获取成员的类型,并定义一个临时指针 __mptr 指向传入的成员指针。

  2. 使用 offsetof 计算成员在结构体中的偏移量。

  3. 将成员指针转换为 char* 类型,减去偏移量,得到结构体的起始地址。

  4. 将起始地址强制转换为结构体指针类型。

container_of 可以使用直接通过指针运算获取结构体地址,无需额外的内存拷贝。适用于任何结构体和成员类型。

相关文章:

  • 【SpringMVC】概述 SSM:Spring + SpringMVC + Mybats
  • 在CentOS安装Docker
  • Redis常用数据类型及其应用案例
  • 机器学习数学基础:30.Pearson相关系数及t检验教程
  • 信息安全实战04_ECC椭圆曲线加密算法原理详解
  • 蓝桥杯试题:区间次方和(前缀和)
  • Gin从入门到精通 (四)请求参数
  • 网络运维学习笔记 022 HCIA-Datacom新增知识点03园区网典型组网架构及案例实战
  • 第一届网谷杯
  • 力扣每日一题【算法学习day.133】
  • 敏捷开发08:如何高效开每日站会(Daily Stand-up Meeting)
  • LEARNING ON LARGE-SCALE TEXT-ATTRIBUTED GRAPHS VIA VARIATIONAL INFERENCE
  • Go语言中使用viper绑定结构体和yaml文件信息时,标签的使用
  • NIO-Reactor模型梳理与demo实现
  • Linux 第三次脚本作业
  • 如何使用智能指针来管理动态分配的内存
  • 函数中的形参和实参(吐槽)
  • R 语言科研绘图 --- 散点图-汇总
  • 记录 idea 启动 tomcat 控制台输出乱码问题解决
  • 嵌入式Linux内核底层调试技术Kprobes
  • 牛市早报|今年第二批810亿元超长期特别国债资金下达,支持消费品以旧换新
  • 豆神教育:2024年净利润1.37亿元,同比增长334%
  • 暗蓝评《性别打结》丨拆解性别之结需要几步?
  • 绵阳造AI机器狗参与警务工作,演练中辅助民警控制“嫌疑人员”
  • 这场迪图瓦纪念拉威尔的音乐会,必将成为乐迷反复品味的回忆
  • 民航局:中方航空公司一季度运输国际旅客同比大增34%