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应用程序包括两部分:
- 列表元素的定义。
- 扩展列表以生成声明或语句的片段。
该列表由一个宏或头文件(名为LIST)定义,该文件本身不生成任何代码,而仅由一系列调用宏(通常称为“ X”)与元素的数据组成。LIST的每个扩展都在X定义之前加上一个list元素的语法。LIST的调用会为列表中的每个元素扩展X。
X-MACRO核心思想有两点:
- 数据与代码分离:将数据定义(如字段、枚举值等)集中在一个地方。
- 多次展开:通过宏的不同定义,将同一份数据生成不同的代码(如枚举、字符串数组、序列化代码等)。
用法介绍
首先我们介绍一下#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
重新定义宏,此时得到的x
,y
的值分别是10
和100
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_MACROS
CMD_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
宏。展开过程:
- 根据
PARTITION_TABLE
的定义,展开为多个PARTITION_MAX_SIZE
调用:
PARTITION_MAX_SIZE(partition_a, 123456)
PARTITION_MAX_SIZE(partition_b, 23456)
// ... 其他分区
- 进一步展开每个
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. 核心目的
通过宏技巧 集中管理分区配置,避免手工编写重复代码:
- 集中化定义:所有分区的名称和大小在
PARTITION_TABLE
中统一配置。 - 自动生成代码:通过宏批量生成对应的静态常量,简化代码维护。
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这种方式,可能是最好的解决办法。
X-MACRO使用技巧的更多相关文章
- Visual Studio高级调试技巧
1. 设置软件断点,运行到目标位置启动调试器 方法①:使用汇编指令(注:x64 c++不支持嵌入汇编) _asm 方法②:编译器提供的方法 __debugbreak(); 方法③:使用windows ...
- Source Insight的应用技巧、宏功能
目录 1 简介... 5 2 搭建我们的SI环境... 5 2.1 搭建步骤... 5 2.2 说明... 6 3 应用技巧... 6 3.1 初级应用技巧... 6 3.1.1 解决字体不等宽与对齐 ...
- 最佳vim技巧
最佳vim技巧----------------------------------------# 信息来源----------------------------------------www.vim ...
- Freemarker的常用技巧总结
Freemarker的常用技巧总结 Freemarker视频教程 1,截取字符串 有的时候我们在页面中不需要显示那么长的字符串,比如新闻标题,这样用下面的例子就可以自定义显示的长度 < lt. ...
- 【技术干货】听阿里云CDN安防技术专家金九讲SystemTap使用技巧
1.简介 SystemTap是一个Linux非常有用的调试(跟踪/探测)工具,常用于Linux 内核或者应用程序的信息采集,比如:获取一个函数里面运行时的变 量.调用堆栈,甚至可以直接修改变量的值, ...
- 《Debug Hacks》和调试技巧【转】
转自:https://blog.csdn.net/sdulibh/article/details/46462529 Debug Hacks 作者为吉冈弘隆.大和一洋.大岩尚宏.安部东洋.吉田俊辅,有中 ...
- 个人在 laravel 开发中使用到的一些技巧(持续更新)
1.更高效率地查询:使用批量查询代替 foreach 查询(多次 io 操作转换为一次 io操作) 如果想要查看更详尽的介绍,可以看看这篇文章 什么是 N+1 问题,以及如何解决 Laravel 的 ...
- 【MFC】MFC技巧学习 当做字典来查
MFC技巧学习 摘自:http://www.cnblogs.com/leven20061001/archive/2012/10/17/2728023.html 1."属性页的添加:创建对话框 ...
- Visual Studio调试之断点技巧篇
原文链接地址:http://blog.csdn.net/Donjuan/article/details/4618717 函数断点 在前面的文章Visual Studio调试之避免单步跟踪调试模式里面我 ...
- 每周分享五个 PyCharm 使用技巧(五)
文章首发于 微信公众号:Python编程时光 大家好,这是本系列 PyCharm 的高效使用技巧的第五篇.按照惯例,本次还是分享 5 个. 本系列前四篇如下,若还没看的,你可以点击查阅 21. 随处折 ...
随机推荐
- C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
前言 C#/.NET/.NET Core技术前沿周刊,你的每周技术指南针!记录.追踪C#/.NET/.NET Core领域.生态的每周最新.最实用.最有价值的技术文章.社区动态.优质项目和学习资源等. ...
- AWVS安装使用
AWVS安装使用 1.双击exe文件,然后点击下一步. 2.选择我接受,然后下一步. 3.选择路径(我选择的默认路径)然后下一步. 4.还是下一步. 5.设置邮箱,用户名密码,用户名12345678@ ...
- nnUNet 使用方法
首先明确分割任务. 其次明确研究方法和步骤. 再做好前期准备,如数据集的采集.标注以及其中的训练集/测试集划分. 其中的参考链接: (四:2020.07.28)nnUNet最舒服的训练教程(让我的奶奶 ...
- docker Get "https://registry-1.docker.io/v2/": x509: certificate is valid for
前言 docker 在进行 build 时,报错:Get "https://registry-1.docker.io/v2/": x509: certificate is vali ...
- python list 差集
前言 有时候我们希望基于list得到一个集合C,该集合C的元素可以被描述为元素在集合A中而不在集合B中.即:差集. 基于set A = [1, 2, 3] B = [2, 3, 4] C = set( ...
- 选择排序(简单版)(LOW)
博客地址:https://www.cnblogs.com/zylyehuo/ # _*_coding:utf-8_*_ def select_sort_simple(li): li_new = [] ...
- study Rust-5【Slice】
另一个没有所有权的数据类型是 slice.slice 允许你引用集合中一段连续的元素序列,而不用引用整个集合. [字符串Slice熟悉掌握的很勉强,通过动手来进步加深认识] 字符串slice let ...
- JavaScript Library – Embla Carousel
前言 2022 年 4 月,我写了一篇 Swiper 介绍. Swiper 是当时前端最多人使用的 Slider 库,没有之一,一骑绝尘. 但是!时过境迁,这两年已经有一匹神秘的黑马悄悄杀上来了. 它 ...
- 运维必备:基于 Harbor 的 Helm Charts 批量拉取,从配置到自动化脚本
引言 在企业级 Kubernetes 环境中,Harbor 作为主流的镜像与 Helm Chart 管理工具,常被用于存储 Helm Charts.但在迁移.备份或离线部署场景中,批量拉取 Harbo ...
- Sentinel源码—4.FlowSlot实现流控的原理
大纲 1.FlowSlot根据流控规则对请求进行限流 2.FlowSlot实现流控规则的快速失败效果的原理 3.FlowSlot实现流控规则中排队等待效果的原理 4.FlowSlot实现流控规则中Wa ...