如何用C预处理器处理逗号分隔宏列表,生成函数指针与字符串数组?
基于逗号分隔令牌宏自动生成函数指针数组与字符串数组
你提出的需求完全可以实现,借助C预处理器的宏连接(##)、字符串化(#)以及可变参数宏的递归展开能力,就能让数组内容随TOKENS宏的变更自动同步。以下是具体实现方案:
一、核心宏定义
首先实现两个核心辅助逻辑:给每个令牌添加_callback后缀,以及将每个令牌转换为对应的字符串。我们可以通过通用的"逐个元素应用宏"工具宏来复用逻辑。
// 原始令牌列表 #define TOKENS ACC, STOP, RUN, BACK // 辅助宏1:给单个令牌添加_callback后缀 #define ADD_CALLBACK(token) token##_callback // 辅助宏2:将单个令牌字符串化 #define STRINGIFY(token) #token // 通用工具宏:逐个将宏应用到列表的每个元素 #define _APPLY_EACH_HELPER(macro, elem, ...) \ macro(elem) \ _IF_HAS_NEXT(__VA_ARGS__, , _APPLY_EACH_HELPER(macro, __VA_ARGS__)) #define _IF_HAS_NEXT(...) \ _IF_HAS_NEXT_HELPER(__VA_ARGS__, 1, 0) #define _IF_HAS_NEXT_HELPER(_1, _2, has_next, ...) has_next #define _APPLY_EACH(macro, ...) \ _APPLY_EACH_HELPER(macro, __VA_ARGS__)
二、生成函数指针数组
利用上面的工具宏,实现some_macro_shenanigans来展开为带后缀的函数指针列表:
#define some_macro_shenanigans(tokens) \ _APPLY_EACH(ADD_CALLBACK, tokens) // 定义对应的回调函数(需与令牌一一对应) int ACC_callback(const char *arg) { /* 自定义逻辑 */ } int STOP_callback(const char *arg) { /* 自定义逻辑 */ } int RUN_callback(const char *arg) { /* 自定义逻辑 */ } int BACK_callback(const char *arg) { /* 自定义逻辑 */ } // 生成函数指针数组 int (*callbacks[])(const char * arg) = { some_macro_shenanigans(TOKENS) };
展开后等价于:
int (*callbacks[])(const char * arg) = { ACC_callback, STOP_callback, RUN_callback, BACK_callback };
三、生成字符串数组
同样复用工具宏,实现some_other_macro_shenanigans来生成字符串数组:
#define some_other_macro_shenanigans(tokens) \ { _APPLY_EACH(STRINGIFY, tokens) } // 生成字符串数组 const char * const token_str[] = some_other_macro_shenanigans(TOKENS);
展开后等价于:
const char * const token_str[] = { "ACC", "STOP", "RUN", "BACK" };
四、完整可运行示例
#include <stdio.h> // 原始令牌列表 #define TOKENS ACC, STOP, RUN, BACK // 辅助宏1:给单个令牌添加_callback后缀 #define ADD_CALLBACK(token) token##_callback // 辅助宏2:将单个令牌字符串化 #define STRINGIFY(token) #token // 通用工具宏:逐个将宏应用到列表的每个元素 #define _APPLY_EACH_HELPER(macro, elem, ...) \ macro(elem) \ _IF_HAS_NEXT(__VA_ARGS__, , _APPLY_EACH_HELPER(macro, __VA_ARGS__)) #define _IF_HAS_NEXT(...) \ _IF_HAS_NEXT_HELPER(__VA_ARGS__, 1, 0) #define _IF_HAS_NEXT_HELPER(_1, _2, has_next, ...) has_next #define _APPLY_EACH(macro, ...) \ _APPLY_EACH_HELPER(macro, __VA_ARGS__) // 生成函数指针数组的宏 #define some_macro_shenanigans(tokens) \ _APPLY_EACH(ADD_CALLBACK, tokens) // 生成字符串数组的宏 #define some_other_macro_shenanigans(tokens) \ { _APPLY_EACH(STRINGIFY, tokens) } // 示例回调函数实现 int ACC_callback(const char *arg) { printf("执行ACC回调,参数:%s\n", arg); return 0; } int STOP_callback(const char *arg) { printf("执行STOP回调,参数:%s\n", arg); return 0; } int RUN_callback(const char *arg) { printf("执行RUN回调,参数:%s\n", arg); return 0; } int BACK_callback(const char *arg) { printf("执行BACK回调,参数:%s\n", arg); return 0; } int main() { // 生成函数指针数组 int (*callbacks[])(const char * arg) = { some_macro_shenanigans(TOKENS) }; // 生成字符串数组 const char * const token_str[] = some_other_macro_shenanigans(TOKENS); // 测试调用所有回调 size_t token_count = sizeof(callbacks) / sizeof(callbacks[0]); for (size_t i = 0; i < token_count; i++) { callbacks[i](token_str[i]); } return 0; }
五、注意事项
- 当
TOKENS列表新增或删除令牌时,只需修改#define TOKENS这一行,数组会自动同步更新,无需手动修改数组元素。 - 回调函数的命名必须严格遵循
令牌名_callback的格式,否则会出现未定义符号的编译错误。 - 上述宏基于标准C预处理器实现,兼容绝大多数编译器(GCC、Clang、MSVC等)。
内容的提问来源于stack exchange,提问作者Qpieloq
相关产品推荐
相关产品推荐

