让宏计算它的调用
Let a macro count its invocations
我有一个巨大的 C 项目,其中包含一个读取和管理配置数据的模块。如果我必须添加一个新的配置参数,我将不得不编辑几个函数,例如作为伪代码:
void read_configuration(config *c) {
read_param("p1", c->p1);
read_param("p2", c->p2);
read_param("p3", c->p3);
/* ... */
}
void dump_configuration(config *c) {
dump_param("p1", c->p1);
dump_param("p2", c->p2);
dump_param("p3", c->p3);
/* ... */
}
有没有办法通过宏在编译时确保每个位置至少有相同数量的参数?我想制作 dump_param
某种宏来计算调用次数,然后添加类似
的内容
#if nr_read != nr_dump
#error "You forgot something, idiot!"
#endif
在模块的末尾。不过,我找不到让宏计算其调用次数的方法...
我认为如果不进行丑陋的黑客攻击,您无法在编译时执行此操作。
您可以做什么:向您的测试套件添加一个测试,替换包含 read_param()
和 dump_param()
宏的 header,以便它们生成仅更新计数器的代码。然后,在该测试的 main()
函数中,放置一个比较两个计数器的断言,如果它们不相等则失败。
您确实有一个测试套件并且 运行 它在编译时,对吧? ;-)
但是,我确实同意以不同方式执行此操作可能更好的评论。在一种称为 "table-driven programming" 的方法中,您将宏定义和数据定义颠倒了过来(也就是说,您在 .c 文件中使用了 #define
,而在 [=25= 中使用了宏] 而不是相反),你没有这个问题。 Poul-Henning Kamp,FreeBSD 的名人,很好地解释了如何 here。
由于两个函数的参数列表相同,如何分解 并避免任何可能的不匹配?
使用 X 宏
#define X_CONFIG_PARAMS(config) \
X("p1", (config).p1) \
X("p2", (config).p2) \
X("p3", (config).p3)
void read_configuration(config *c) {
#define X(name, param) read_param(name, ¶m);
X_CONFIG_PARAMS(*c)
#undef X
}
void dump_configuration(config *c) {
#define X(name, param) dump_param(name, ¶m);
X_CONFIG_PARAMS(*c)
#undef X
}
使用函数指针
void alter_config(config *c, void(*func)(char const *name, Param *param)) {
func("p1", &c->p1);
func("p2", &c->p2);
func("p3", &c->p3);
}
void read_configuration(config *c) {
alter_config(c, read_param);
}
void dump_configuration(config *c) {
alter_config(c, dump_param);
}
使用数组和offsetof
struct param_info {
char const *name;
size_t config_offs;
};
param_info allParams[] = {
{"p1", offsetof(config, p1)},
{"p2", offsetof(config, p2)},
{"p3", offsetof(config, p3)}
};
void read_configuration(config *c) {
size_t paramCount = sizeof allParams / sizeof *allParams;
for(size_t i = 0; i < paramCount; ++i) {
Param *p = (Param*)((char*)config + allParams[i].config_offs);
read_param(allParams[i].name, p);
}
}
void dump_configuration(config *c) {
size_t paramCount = sizeof allParams / sizeof *allParams;
for(size_t i = 0; i < paramCount; ++i) {
Param *p = (Param*)((char*)config + allParams[i].config_offs);
dump_param(allParams[i].name, p);
}
}
我宁愿让预处理器首先编写代码。
它可能看起来像这样:
在单独的文件中定义参数列表,比如 parameters.inc
:
PARAM (p1)
PARAM (p2)
...
然后在源码本地根据需要定义宏PARAM
,让预处理器包含展开parameters.inc
:
的内容
void read_configuration(config *c) {
#define PARAM(NAME) read_param(#NAME, c->NAME);
#include "parameters.inc"
#undef PARAM
}
void dump_configuration(config *c) {
#define PARAM(NAME) dump_param(#NAME, c->NAME);
#include "parameters.inc"
#undef PARAM
}
我有一个巨大的 C 项目,其中包含一个读取和管理配置数据的模块。如果我必须添加一个新的配置参数,我将不得不编辑几个函数,例如作为伪代码:
void read_configuration(config *c) {
read_param("p1", c->p1);
read_param("p2", c->p2);
read_param("p3", c->p3);
/* ... */
}
void dump_configuration(config *c) {
dump_param("p1", c->p1);
dump_param("p2", c->p2);
dump_param("p3", c->p3);
/* ... */
}
有没有办法通过宏在编译时确保每个位置至少有相同数量的参数?我想制作 dump_param
某种宏来计算调用次数,然后添加类似
#if nr_read != nr_dump
#error "You forgot something, idiot!"
#endif
在模块的末尾。不过,我找不到让宏计算其调用次数的方法...
我认为如果不进行丑陋的黑客攻击,您无法在编译时执行此操作。
您可以做什么:向您的测试套件添加一个测试,替换包含 read_param()
和 dump_param()
宏的 header,以便它们生成仅更新计数器的代码。然后,在该测试的 main()
函数中,放置一个比较两个计数器的断言,如果它们不相等则失败。
您确实有一个测试套件并且 运行 它在编译时,对吧? ;-)
但是,我确实同意以不同方式执行此操作可能更好的评论。在一种称为 "table-driven programming" 的方法中,您将宏定义和数据定义颠倒了过来(也就是说,您在 .c 文件中使用了 #define
,而在 [=25= 中使用了宏] 而不是相反),你没有这个问题。 Poul-Henning Kamp,FreeBSD 的名人,很好地解释了如何 here。
由于两个函数的参数列表相同,如何分解 并避免任何可能的不匹配?
使用 X 宏
#define X_CONFIG_PARAMS(config) \
X("p1", (config).p1) \
X("p2", (config).p2) \
X("p3", (config).p3)
void read_configuration(config *c) {
#define X(name, param) read_param(name, ¶m);
X_CONFIG_PARAMS(*c)
#undef X
}
void dump_configuration(config *c) {
#define X(name, param) dump_param(name, ¶m);
X_CONFIG_PARAMS(*c)
#undef X
}
使用函数指针
void alter_config(config *c, void(*func)(char const *name, Param *param)) {
func("p1", &c->p1);
func("p2", &c->p2);
func("p3", &c->p3);
}
void read_configuration(config *c) {
alter_config(c, read_param);
}
void dump_configuration(config *c) {
alter_config(c, dump_param);
}
使用数组和offsetof
struct param_info {
char const *name;
size_t config_offs;
};
param_info allParams[] = {
{"p1", offsetof(config, p1)},
{"p2", offsetof(config, p2)},
{"p3", offsetof(config, p3)}
};
void read_configuration(config *c) {
size_t paramCount = sizeof allParams / sizeof *allParams;
for(size_t i = 0; i < paramCount; ++i) {
Param *p = (Param*)((char*)config + allParams[i].config_offs);
read_param(allParams[i].name, p);
}
}
void dump_configuration(config *c) {
size_t paramCount = sizeof allParams / sizeof *allParams;
for(size_t i = 0; i < paramCount; ++i) {
Param *p = (Param*)((char*)config + allParams[i].config_offs);
dump_param(allParams[i].name, p);
}
}
我宁愿让预处理器首先编写代码。
它可能看起来像这样:
在单独的文件中定义参数列表,比如 parameters.inc
:
PARAM (p1)
PARAM (p2)
...
然后在源码本地根据需要定义宏PARAM
,让预处理器包含展开parameters.inc
:
void read_configuration(config *c) {
#define PARAM(NAME) read_param(#NAME, c->NAME);
#include "parameters.inc"
#undef PARAM
}
void dump_configuration(config *c) {
#define PARAM(NAME) dump_param(#NAME, c->NAME);
#include "parameters.inc"
#undef PARAM
}