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

嵌入式科普(38)C语言预编译X-Macros深度分析和实际项目代码分享

一、概述

  • X-Macros是C语言一种古老的、编译时代码生成技术,并不是数据结构。简单说是一种代码技巧,是一种复杂的宏。

  • 介绍X-Macros的工作流程和主要特征

  • 通过经典例子介绍使用X-Macros代码的运行时的工作原理

  • 实际项目代码分享:西门子profidrive使用X-Macros技术的源码

二、参考资料

  • https://zhuanlan.zhihu.com/p/521073931

  • https://www.drdobbs.com/cpp/the-new-c-x-macros/184401387

  • https://tetzank.github.io/posts/x-macros/

  • https://github.com/garcia/xdata

  • https://github.com/swansontec/map-macro

  • 瑞萨RZN2L产品-第22章 分析西门子profidrive使用X-Macros技术的源码

三、经典X-Macros代码分析

3.1 经典代码

#include <stdio.h>#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;/*定义字符串列表用作Log打印*/
const char* cmd_str[] = 
{#define X_MACROS(a, b) #a,MACROS_TABLE#undef X_MACROS
};typedef void (*func)(void* p);static void led_on(void* p)
{printf("%s \r\n", (char *)p);
}static void led_off(void* p)
{printf("%s \r\n", (char *)p);
}/*定义函数列表*/
const func func_table[] = 
{#define X_MACROS(a, b) b,MACROS_TABLE#undef X_MACROS
};/*直接通过索引的方式调用函数*/
static void cmd_handle(cmd_e cmd)
{if(cmd < CMD_MAX){func_table[cmd]((void*)cmd_str[cmd]);}
}void main(void)
{cmd_handle(CMD_LED_ON);cmd_handle(CMD_LED_OFF);
}

3.2 代码结构解析(四步构建自动化系统)

图片

3.2.1 中央数据表(唯一数据源)

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

这是系统的唯一数据源,定义了:

  • 命令枚举名 CMD_LED_ON

  • 对应函数名 led_on

  • 自动保持顺序一致性

3.2.2 动态生成枚举

typedef enum {#define X_MACROS(a, b) a,  // 展开为 CMD_LED_ON, CMD_LED_OFFMACROS_TABLE#undef X_MACROSCMD_MAX  // 自动计算命令总数
} cmd_e;

展开后等价于:

typedef enum {CMD_LED_ON, CMD_LED_OFF,CMD_MAX
} cmd_e;

3.2.3 自动生成字符串表

const char* cmd_str[] = {#define X_MACROS(a, b) #a,  // 字符串化:CMD_LED_ON -> "CMD_LED_ON"MACROS_TABLE#undef X_MACROS
};

展开效果:

const char* cmd_str[] = {"CMD_LED_ON", "CMD_LED_OFF"
};

3.2.4 函数指针表自动映射(核心执行逻辑)

const func func_table[] = {#define X_MACROS(a, b) b,  // 提取函数名:led_on, led_offMACROS_TABLE#undef X_MACROS
};

等价于:

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

3.3 运行时工作原理

图片

四、优劣势解析

  • 零同步成本
    所有关联数据(枚举/字符串/函数)自动同步,避免人工维护不一致

  • 扩展极简
    新增命令只需在宏表中添加一行:
    X_MACROS(CMD_DOOR_OPEN, door_open)
    
  • 安全保证
    CMD_MAX 自动计算数组边界,防止越界访问

  • 自文档化
    字符串表自动生成,日志输出与代码完全一致

  • 代码可读性降低

  • 绝大多数的阅读软件或IDE无法预览/展开
    需要编译器在预编译阶段宏展开

五、X-Macros的四大本质特征

图片

5.1 集中式数据表

用 #define 定义的垂直结构表,每个条目包含关联数据

典型形式:

#define COLOR_TABLE \ENTRY(RED,    0xFF0000) \ENTRY(GREEN,  0x00FF00) \ENTRY(BLUE,   0x0000FF)

5.2 可重定义的宏函数

名为 ENTRY 或 X 的通用处理宏

通过重定义改变其行为

/* 阶段1:生成枚举 */
#define ENTRY(name, value) COLOR_##name,/* 阶段2:生成字符串 */
#define ENTRY(name, value) #name,

5.3 多次展开机制

同一数据表被多次使用,每次生成不同代码

// 第一次展开:生成枚举
enum Colors {COLOR_TABLE  // 展开为 COLOR_RED, COLOR_GREEN...
};// 第二次展开:生成字符串数组
const char* color_names[] = {COLOR_TABLE  // 展开为 "RED", "GREEN"...
};

5.4 生成关联结构

输出逻辑相关但语法不同的代码结构

六、为什么VSCode无法展开X-Macro?

  • IDE 限制:
    VSCode 的 C/C++ 扩展基于 Clang/IntelliSense
    这些工具不模拟完整的预处理流程(尤其多次重定义的宏)
    只能显示当前作用域的宏定义,无法展示多阶段展开

  • 宏的工作机制:

      #define X_MACROS(a, b) a,  // 阶段1:定义枚举MACROS_TABLE             // 此处展开#undef X_MACROS          // 立即取消定义#define X_MACROS(a, b) #a, // 阶段2:定义字符串MACROS_TABLE             // 重新展开
    

    VSCode 只能显示最后一次展开的结果

  • 预处理 vs 编辑时:

    • X-Macro 是预处理阶段的特性

    • VSCode 是源代码编辑器,不执行完整预处理

6.1 解决方案

6.1.1 使用编译器预处理(推荐)

# GCC/Clang
gcc -E your_file.c -o expanded.c
// 枚举展开后
typedef enum
{CMD_LED_ON,CMD_LED_OFF,CMD_MAX
} cmd_e;// 字符串表展开后
const char* cmd_str[] = 
{"CMD_LED_ON","CMD_LED_OFF"
};// 函数表展开后
const func func_table[] = 
{led_on,led_off
};

6.1.2 VSCode 插件辅助

  • C/C++ Advanced Lint:显示宏展开警告

  • Preprocessor Macro Expander:手动展开选中宏

  • Clangd(替代默认C++扩展):更好的宏支持

6.2 深入探讨宏处理和 IDE 支持的本质区别

6.2.1 所有宏都是预处理的,但 IDE 支持分层次

宏类型

预处理阶段

IDE 支持级别

VSCode 悬停效果

简单对象宏


#define PI 3.14

✅ 完全支持

★★★★★

显示 3.14

函数式宏


#define MIN(x,y) ((x)<(y)?(x):(y))

✅ 完全支持

★★★★☆

显示展开表达式

链式宏

(多层嵌套宏)

✅ 完全支持

★★★☆☆

部分展开

X-Macro

(依赖重定义和多次展开)

✅ 完全支持

★☆☆☆☆

通常无法显示

6.2.2 关键差异:宏展开的"阶段可见性"

  • 预处理器:执行完整递归展开(深度优先)

  • IDE 语法解析器:执行浅层展开(通常只展开1-2层)

    图片

6.2.3 为什么普通宏可见而X-Macros不可见?

  • 普通宏 IDE 只需简单替换即可展示

#define CIRCLE_AREA(r) (PI * (r) * (r))// 悬停在 CIRCLE_AREA(5) 显示:
// (3.1415926 * (5) * (5))
  • X-Macros示例(VSCode 难解析):
    IDE 无法同时保存两种 ENTRY 定义状态

    ENTRY(red) \ENTRY(blue)// 第一次展开
#define ENTRY(color) COLOR_##color,
enum Colors {TABLE
};
// 期望:COLOR_red, COLOR_blue// 第二次展开(不同定义)
#define ENTRY(color) #color,
const char* color_names[] = {TABLE
};
// 期望:"red", "blue"
  • 预处理器的完整工作流程

    图片

  • IDE 的实时解析

    图片

七、比X-Macros更复杂的宏

  • X-Macros只是宏技术的"中级阶段"

    图片

  • 宏的替代方案:

    • C++ 模板:类型安全的泛型

    • C++ constexpr:编译时计算

    • 代码生成器:Python/Lua 脚本

    • LLVM 插件:编译时元编程

    • 专用预处理器:如 Qt 的 moc

八、西门子profidrive使用X-Macros技术的源码

/* for parameter descriptions see PDRV V4.2 table 144 and others *//* PNU00001..PNU00899: device specific, insert your parameters here -------------------------------------------------*//** PNU00100: gradient of ramp in [%/ms] for the Ramp Function Generator* @details Setting parameter for the Ramp Function Generator (PDRV V4.2 Figure 29)*          application specific, can be changed by the PDRV user*/
PDRV_PARAMETER(100U,               /**< parameter number */(PDRV_PARID_N4),    /**< identifier */0U,                 /**< number of elements or length of string */PDRV_UNIT_PCT,      /**< variable attribute */60000U,             /**< DO IO DATA reference parameter */0x801EU,            /**< DO IO DATA normalisation */FLOAT_N4_FAC,       /**< standardisation factor */0U,                 /**< low limit */0x10000000U,        /**< high limit */"Ramp Gradient",    /**< pointer at name (16 valid characters) */PDRV_NULL_T,        /**< function pointer - function is called if additional text is read */uPdrv_RfPnu00100,   /**< function pointer - function is called before value is read */uPdrv_WfPnu00100    /**< function pointer - function is called after value is written */
)/** PNU00110: +- allowed speed tolerance for ZSW1 bit 8 "speed errror within tolerance range"* @details Setting parameter for calculation of ZSW1 bit 8 (PDRV V4.2 Figure 29)*          application specific, can be changed by the PDRV user*/
PDRV_PARAMETER(110U,               /**< parameter number */(PDRV_PARID_N4),    /**< identifier */0U,                 /**< number of elements or length of string */PDRV_UNIT_PCT,      /**< variable attribute */60000U,             /**< DO IO DATA reference parameter */0x801EU,            /**< DO IO DATA normalisation */FLOAT_N4_FAC,       /**< standardisation factor */0U,                 /**< low limit */0x7FFFFFFFU,        /**< high limit */"range speederror", /**< pointer at name (16 valid characters) */PDRV_NULL_T,        /**< function pointer - function is called if additional text is read */uPdrv_RfPnu00110,   /**< function pointer - function is called before value is read */uPdrv_WfPnu00110    /**< function pointer - function is called after value is written */
)/** pre declaration of parameter object */
typedef struct PDRV_PAR_OBJ PDRV_PAR_OBJ;/** parameter object inclusive description elements (see PDRV V4.2 table 17)* @details no reserved datas, different order from PDRV description, order is arbitrary (consider alignment)*/
struct PDRV_PAR_OBJ
{PDRV_UINT16 uPnu;           /**< parameter number */PDRV_UINT16 uIdentifier;    /**< identifier */PDRV_UINT16 uNrOfElements;  /**< number of elements or length of string */PDRV_UINT16 uVarAttrib;     /**< variable attribute */PDRV_UINT16 uRefPar;        /**< DO IO DATA reference parameter */PDRV_UINT16 uNormalisation; /**< DO IO DATA normalisation */PDRV_FLT32  fStdFactor;     /**< standardisation factor */PDRV_UINT32 uLoLimit;       /**< low limit */PDRV_UINT32 uHiLimit;       /**< high limit */const char* puName;         /**< pointer at name */char* (*pfnText)(const PDRV_PAR_OBJ *p_ptParObj, PDRV_UINT16 p_uSubindex); /**< function pointer - function is called if additional text is read */PDRV_UINT32 (*pfnRead)(const PDRV_PAR_OBJ *p_ptParObj, PDRV_UINT16 p_uSubindex, PDRV_UINT16 p_uNrOfElements, PDRV_ParValues * p_ptValues); /**< function pointer - function is called before value is read */PDRV_UINT32 (*pfnWrite)(const PDRV_PAR_OBJ *p_ptParObj, PDRV_UINT16 p_uSubindex, PDRV_UINT16 p_uNrOfElements, PDRV_ParValues * p_ptValues); /**< function pointer - function is called after value is written */
};/*------------  extern  functions  ------------*/
/** declaration of all text functions */
#define PDRV_PARAMETER(Pnu, Identifier, NrOfElements, VarAttrib, RefPar, Normalisation, StdFactor, LoLimit, HiLimit, Name, TextFunc, ReadFunc, WriteFunc) \extern char * TextFunc (PDRV_PAR_OBJ const *p_ptParObj, PDRV_UINT16 p_uSubindex);#include "pdrv_parameter_ac4.inc"#undef PDRV_PARAMETER/** declaration of all read functions */
#define PDRV_PARAMETER(Pnu, Identifier, NrOfElements, VarAttrib, RefPar, Normalisation, StdFactor, LoLimit, HiLimit, Name, TextFunc, ReadFunc, WriteFunc) \extern PDRV_UINT32 ReadFunc (PDRV_PAR_OBJ const *p_ptParObj, PDRV_UINT16 p_uSubindex, PDRV_UINT16 p_uNrOfElements, PDRV_ParValues * p_ptValues);#include "pdrv_parameter_ac4.inc"#undef PDRV_PARAMETER/** declaration of all read functions */
#define PDRV_PARAMETER(Pnu, Identifier, NrOfElements, VarAttrib, RefPar, Normalisation, StdFactor, LoLimit, HiLimit, Name, TextFunc, ReadFunc, WriteFunc) \extern PDRV_UINT32 WriteFunc (PDRV_PAR_OBJ const *p_ptParObj, PDRV_UINT16 p_uSubindex, PDRV_UINT16 p_uNrOfElements, PDRV_ParValues * p_ptValues);#include "pdrv_parameter_ac4.inc"#undef PDRV_PARAMETER/*------------  extern  data  ------------*//*------------  type definitions, constants, enums  ------------*//** complete list of all parameters for implementation of parameters PNU00980 to PNU00989 */
static const PDRV_O2 m_tParList[] =
{
#define PDRV_PARAMETER(Pnu, Identifier, NrOfElements, VarAttrib, RefPar, Normalisation, StdFactor, LoLimit, HiLimit, Name, TextFunc, ReadFunc, WriteFunc) \Pnu,
#include "pdrv_parameter_ac4.inc"
#undef PDRV_PARAMETER
};#define PDRV_NOOFPARAMETERS (sizeof(m_tParList)/sizeof(m_tParList[0]))  /**< number of all parameters *//** table with parameter objects */
static const PDRV_PAR_OBJ m_tParObjDatas[] =
{
#define PDRV_NULL_T PDRV_NULL   /**< redefinition to NULL pointer */
#define PDRV_NULL_R PDRV_NULL   /**< redefinition to NULL pointer */
#define PDRV_NULL_W PDRV_NULL   /**< redefinition to NULL pointer */
#define PDRV_PARAMETER(Pnu, Identifier, NrOfElements, VarAttrib, RefPar, Normalisation, StdFactor, LoLimit, HiLimit, Name, TextFunc, ReadFunc, WriteFunc) \{.uPnu = Pnu, \.uIdentifier = Identifier, \.uNrOfElements = NrOfElements, \.uVarAttrib = VarAttrib, \.uRefPar = RefPar, \.uNormalisation = Normalisation, \.fStdFactor = StdFactor, \.uLoLimit = LoLimit, \.uHiLimit = HiLimit, \.puName = Name, \.pfnText = TextFunc, \.pfnRead = ReadFunc, \.pfnWrite = WriteFunc},#include "pdrv_parameter_ac4.inc"#undef PDRV_PARAMETER
#undef PDRV_NULL_W
#undef PDRV_NULL_R
#undef PDRV_NULL_T
};/** PROFIdrive search for parameter number and get pointer of found parameter object*  @details*  @return     pointer with parameter object, PDRV_NULL if not found
*/
const PDRV_PAR_OBJ * ptPdrvPar_GetParObj(PDRV_UINT16 p_uPnu     /**< [in] search this parameter number */)
{PDRV_PAR_OBJ const *ptParObj = PDRV_NULL;PDRV_UINT uI;for(uI = 0U; uI < PDRV_NOOFPARAMETERS; uI++){/* parameter found? */if (m_tParList[uI] == p_uPnu){ptParObj = &m_tParObjDatas[uI];break;}}return ptParObj;
}

九、总结

  • X-Macros是一种代码技术/技巧,适用于中小型项目中。

  • X-Macros在Linux内核和工业自动化领域非常普遍(如西门子 SINAMICS、倍福 TwinCAT)

  • 推荐使用gcc -E命令在预编译阶段展开

  • 我们在后续的瑞萨RZN2L产品-第22章 分析西门子profidrive使用X-Macros技术的源码

图片


文章转载自:

http://TWv0wkwk.Lhwmr.cn
http://yHc5rQj5.Lhwmr.cn
http://abCGB0yR.Lhwmr.cn
http://ChN4kvVD.Lhwmr.cn
http://wLGvpNa8.Lhwmr.cn
http://nE5UYTMp.Lhwmr.cn
http://QjNrgDkz.Lhwmr.cn
http://w6qL2Znd.Lhwmr.cn
http://KcaFWVmj.Lhwmr.cn
http://lEZyxyc1.Lhwmr.cn
http://CwOfOS1b.Lhwmr.cn
http://uNE0yYBV.Lhwmr.cn
http://LTG5iKNT.Lhwmr.cn
http://7fVKzEFn.Lhwmr.cn
http://6Hl2Y0nj.Lhwmr.cn
http://3toIKAvG.Lhwmr.cn
http://Kps9EwBH.Lhwmr.cn
http://nKvXkon6.Lhwmr.cn
http://kppPnOPH.Lhwmr.cn
http://uwCevxXf.Lhwmr.cn
http://1qcwkduZ.Lhwmr.cn
http://0pL25Yrx.Lhwmr.cn
http://FXhJP2YR.Lhwmr.cn
http://p9tb5i8d.Lhwmr.cn
http://oE69r7Z7.Lhwmr.cn
http://BZMl6hqg.Lhwmr.cn
http://yVcDYIGr.Lhwmr.cn
http://9ia1qMSH.Lhwmr.cn
http://2qddImi2.Lhwmr.cn
http://ZvkygJdm.Lhwmr.cn
http://www.dtcms.com/a/387589.html

相关文章:

  • Docker compose 与 docker swarm 的区别
  • 【嵌入式硬件实例】-555定时器实现水位检测
  • AbMole小课堂丨R-spondin-1(RSPO1):高活性Wnt通路激活剂,如何在多种类器官/干细胞培养中发挥重要功能
  • 【C语言代码】打印九九乘法口诀表
  • vue3和element plus, node和express实现大文件上传, 分片上传,断点续传完整开发代码
  • electron-egg使用ThinkPHP项目指南
  • 温州工业自动化科技工厂如何实现1台服务器10个研发设计同时用
  • 如何用PM2托管静态文件
  • Java程序设计:基本数据类型
  • 在k8s环境下部署kanboard项目管理平台
  • 为什么 MySQL utf8 存不下 Emoji?utf8mb4 实战演示
  • 2025 年 PHP 常见面试题整理以及对应答案和代码示例
  • (二十五)、在 k8s 中部署证书,为网站增加https安全认证
  • 风机巡检目前有什么新技术?
  • 震坤行工业超市开放平台接口实战:工业品精准检索与详情解析全方案
  • 河南萌新联赛2025第(八)场:南阳理工学院
  • docker回收和mysql备份导入导致数据丢失恢复---惜分飞
  • 「Memene 摸鱼日报 2025.9.17」上海张江人工智能创新小镇正式启动,华为 DCP 技术获网络顶会奖项
  • 【数据结构】顺序表,ArrayList
  • 第十二章 Arm C1-Premium GIC CPU接口详解
  • 【数据结构---并查集】(并查集的原理,实现与应用)
  • 【数据结构-KMP算法(学习篇)】
  • Start application catch exception
  • 机器视觉在半导体封装检测中的应用
  • 雅菲奥朗SRE知识墙分享(九):『变更管理的定义与实践』
  • 51c视觉~3D~合集6
  • webRTC 的协议族
  • 线激光相机 眼在手上六轴机器人手眼标定 备忘记录
  • QML学习笔记(一)基本了解和工程配置
  • 大数据毕业设计选题推荐-基于大数据的牛油果数据可视化分析系统-Hadoop-Spark-数据可视化-BigData