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

X-MACRO使用技巧

背景

最近遇到一个问题,需要将分区表硬编码在代码,第一反应可能是定义个数组,数组内容包括分区名称和分区大小。

类似于这种:

struct Partition {const char *name;int max_size;
};
static const struct Partition partitions[] = {{"partition_a", 0x12345},{"partition_b", 0x12345},// ...
};

但在设备启动时,每加载一个分区时都要获取分区大小。如果有16个分区就需要遍历该数组16 * 16 = 256次。会浪费不少时间在这上面。   因此,考虑到对设备启动时间的影响,需要找一种其他办法来解决这个问题。

什么是xmacro

X-MACRO是一种可靠维护代码或数据的并行列表的技术,其相应项必须以相同的顺序出现。它们在至少某些列表无法通过索引组成的地方(例如编译时)最有用。此类列表的示例尤其包括数组的初始化,枚举常量和函数原型的声明,语句序列的生成等。X-MACRO的使用可以追溯到1960年代。它在现代C和C ++编程语言中仍然有用。

X-MACRO应用程序包括两部分:

  1. 列表元素的定义。
  2. 扩展列表以生成声明或语句的片段。

该列表由一个宏或头文件(名为LIST)定义,该文件本身不生成任何代码,而仅由一系列调用宏(通常称为“ X”)与元素的数据组成。LIST的每个扩展都在X定义之前加上一个list元素的语法。LIST的调用会为列表中的每个元素扩展X。

X-MACRO核心思想有两点:

  1. 数据与代码分离:将数据定义(如字段、枚举值等)集中在一个地方。
  2. 多次展开:通过宏的不同定义,将同一份数据生成不同的代码(如枚举、字符串数组、序列化代码等)。

用法介绍

首先我们介绍一下#define#undef的用法:

#define X_MACRO(a, b)   a
#undef X_MACRO
#define X_MACRO(a, b)   b
#undef X_MACRO

示例:

#define X_MACRO(a, b)   a
int x = X_MACRO(10, 100)
#undef X_MACRO#define X_MACRO(a, b)   b
int y = X_MACRO(10, 100)
#undef X_MACRO

#undef可以取消定义宏,然后再通过#define重新定义宏,此时得到的xy的值分别是10100


X-Macro其实就是通过#define#undef实现的一种宏定义的技巧;

首先我们可以定义出这样的宏列表:

#define MACROS_TABLE                    \X_MACROS(CMD_LED_ON,  led_on)       \X_MACROS(CMD_LED_OFF, led_off)      \

当我们需要一个命令列表时可以这样定义:

typedef enum
{#define X_MACROS(a, b) a,MACROS_TABLE#undef X_MACROSCMD_MAX
}cmd_e;

宏展开后是这样的形式:

typedef enum
{CMD_LED_ON,CMD_LED_OFF,CMD_MAX
}cmd_e;

如果我们需要一个命令的字符串列表用作log打印时也可以定义这样的列表:

const char* cmd_str[] = 
{#define X_MACROS(a, b) #a,MACROS_TABLE#undef X_MACROS
};

宏展开后是这样的形式:

const func func_table[] = 
{“CMD_LED_ON”,“CMD_LED_OFF”,
};

当我们需要一个函数列表时可以这样操作:

const func func_table[] = 
{#define X_MACROS(a, b) b,MACROS_TABLE#undef X_MACROS
};

宏展开后是这样的形式:

const func func_table[] = 
{led_on,led_off,
};

由于函数列表与命令列表都是根据MACROS_TABLE这个宏拓展出来的,是一一对应的,所以我们可以直接使用索引的方式来调用函数:

static void cmd_handle(cmd_e cmd)
{if(cmd < CMD_MAX){func_table[cmd]((void*)cmd_str[cmd]);}
}

使用X-MACRO对于此类的命令消息处理十分高效简洁,非常实用,且拓展性非常强。

使用X-MACRO定义分区大小

/**
* Maximum partition size
*/
#define PARTITION_TABLE      \PARTITION_MAX_SIZE(partition_a, 123456)    \PARTITION_MAX_SIZE(partition_b, 23456)      \............#define PARTITION_MAX_SIZE(name, size) \static const int PART_##name##_SIZE = size;PARTITION_TABLE
#undef PARTITION_MAX_SIZE

然后使用get_partition_max_size函数获取分区的大小。

int get_partition_max_size(const char *partition_name)
{return 0#define PARTITION_MAX_SIZE(name, size) +(strcmp(partition_name, #name) ? 0 : PART_##name##_SIZE)PARTITION_TABLE#undef PARTITION_MAX_SIZE;
}

下面对这函数做详细介绍。

以上代码主要分为两部分。第一次定义 PARTITION_MAX_SIZE 生成静态常量。第二次在get_partition_max_size函数内重新定义,生成条件判断表达式。

PARTITION_MAX_SIZE生成静态常量

1. 定义分区表宏 PARTITION_TABLE
#define PARTITION_TABLE      \PARTITION_MAX_SIZE(partition_a, 123456)    \PARTITION_MAX_SIZE(partition_b, 23456)      \// ... 其他分区定义
  • 作用
    通过宏 PARTITION_MAX_SIZE 定义多个分区的名称和大小,形成一张分区表。

  • 示例内容

    • partition_a 的大小为 123456 字节。
    • partition_b 的大小为 23456 字节。
    • ... 表示可以继续添加更多分区。

2 . 定义宏 PARTITION_MAX_SIZE
#define PARTITION_MAX_SIZE(name, size) \static const int PART_##name##_SIZE = size;
  • 作用
    PARTITION_MAX_SIZE(name, size) 转换为静态常量定义,格式为:
    static const int PART_<name>_SIZE = size;

  • 关键符号

    • ##:宏的拼接运算符,将 name 插入到 PART__SIZE 之间。
  • 示例
    PARTITION_MAX_SIZE(partition_a, 123456) 展开后:

    static const int PART_partition_a_SIZE = 123456;
    

3 . 展开 PARTITION_TABLE
PARTITION_TABLE
  • 作用
    展开 PARTITION_TABLE 宏,实际调用其中所有 PARTITION_MAX_SIZE 宏。

  • 展开过程

    1. 根据 PARTITION_TABLE 的定义,展开为多个 PARTITION_MAX_SIZE 调用:
    PARTITION_MAX_SIZE(partition_a, 123456)
    PARTITION_MAX_SIZE(partition_b, 23456)
    // ... 其他分区
    
    1. 进一步展开每个 PARTITION_MAX_SIZE
    static const int PART_partition_a_SIZE = 123456;
    static const int PART_partition_b_SIZE = 23456;
    // ... 其他分区对应的静态变量
    

4 . 取消宏定义
#undef PARTITION_MAX_SIZE
  • 作用
    取消 PARTITION_MAX_SIZE 的宏定义,防止后续代码中可能出现的宏名冲突。

5. 完整展开示例

假设 PARTITION_TABLE 定义如下:

#define PARTITION_TABLE      \PARTITION_MAX_SIZE(partition_a, 123456)    \PARTITION_MAX_SIZE(partition_b, 23456)

则代码:

#define PARTITION_MAX_SIZE(name, size) \static const int PART_##name##_SIZE = size;
PARTITION_TABLE
#undef PARTITION_MAX_SIZE

将展开为:

static const int PART_partition_a_SIZE = 123456;
static const int PART_partition_b_SIZE = 23456;
6. 核心目的

通过宏技巧 集中管理分区配置,避免手工编写重复代码:

  1. 集中化定义:所有分区的名称和大小在 PARTITION_TABLE 中统一配置。
  2. 自动生成代码:通过宏批量生成对应的静态常量,简化代码维护。

get_partition_max_size 函数逻辑

get_partition_max_size 函数内部重新定义了 PARTITION_MAX_SIZE,每个 PARTITION_MAX_SIZE(name, size) 被替换为:

+ (strcmp(partition_name, "partition_a") ? 0 : PART_partition_a_SIZE)+ (strcmp(partition_name, "partition_b") ? 0 : PART_partition_b_SIZE)

最终在get_partition_max_size函数展开为:

int get_partition_max_size(const char *partition_name) {return 0+ (strcmp(partition_name, "partition_a") ? 0 : PART_partition_a_SIZE)+ (strcmp(partition_name, "partition_b") ? 0 : PART_partition_b_SIZE)
}

partition_name 匹配 "parititon_a",表达式结果为 PART_partition_a_SIZE(即 123456)。
若不匹配,结果为 0。

总结

相比于使用数组定义分区名称和大小的方式,使用X-MACRO方式大大节省了运行时的开销。所有分区的名称和大小在编译期展开为静态常量,查询时直接访问内存地址,无计算开销。而且静态常量存储在程序的只读数据段(如 .rodata),不占用堆栈内存。

当然,缺点也很明显,代码可读性,可调试性会下降。

优点缺点总是相对而论的,在某些情况下,缺点也可以变为优点。而且,并不是所有的方案都完美无缺。需求的优先级是最高的,这点毋庸置疑。在考虑性能和可维护性的前提下,使用X-MACRO这种方式,可能是最好的解决办法。

http://www.xdnf.cn/news/10366.html

相关文章:

  • linux命令 systemctl 和 supervisord 区别及用法解读
  • 【计算机网络】Linux下简单的UDP服务器(超详细)
  • 鸿蒙OSUniApp PWA开发实践:打造跨平台渐进式应用#三方框架 #Uniapp
  • Android第十二次面试-多线程和字符串算法总结
  • Mac电脑上本地安装 redis并配置开启自启完整流程
  • uni-app学习笔记十九--pages.json全局样式globalStyle设置
  • Langchaine4j 流式输出 (6)
  • NodeJS全栈开发面试题讲解——P2Express / Nest 后端开发
  • B站视频下载器 v1.0.4|免登录下载1080P视频
  • Global Security Markets 第5章知识点总结
  • 字节面试手撕题:版本号排序
  • ReLU的变体
  • 基于ZYNQ ARM+FPGA异构平台的声呐数据采集系统设计
  • Amazon Augmented AI:人类智慧与AI协作,破解机器学习审核难题
  • 【配置vscode默认终端为git bash】
  • ArcGIS Pro 创建渔网格网过大,只有几个格网的解决方案
  • (面试)OkHttp实现原理
  • AWS之迁移与传输服务
  • Java八股文智能体——Agent提示词(Prompt)
  • linux 后记
  • [总结]前端性能指标分析、性能监控与分析、Lighthouse性能评分分析
  • 如何用docker部署ELK?
  • vue笔记-路由
  • Java抽象工厂模式详解
  • 【STM32F1标准库】理论——定时器/计数器中断
  • CMake指令:add_executable
  • 79. Word Search
  • flask pyinstaller打包exe,出现module not found问题
  • 论文阅读(六)Open Set Video HOI detection from Action-centric Chain-of-Look Prompting
  • 终结电源反接与压降损耗:理想二极管控制器深度解析