定义一个宏,来检测另一个宏是否被定义的优化代码

风景如画

Posted by Bruce Lee on 2024-03-23

定义一个宏,来检测另一个宏是否被定义的优化代码

该问题的原提出者见stackoverflow:

https://stackoverflow.com/questions/26099745/test-if-preprocessor-symbol-is-defined-inside-macro

关于这个宏定义:

1
#define TRACE(x) "" #x

#x是c预处理器的一个操作符,将操作数转换位一个字符串字面量.

即有如此代码

1
printf(TRACE(this_macro));

便会输出this_macro

这里的问题就是为何在#x前面加一个空字符串""

对于

1
2
#define TRACE2(x) #x
#define TRACE3(x) "" #x

这两种不同的宏定义,具体表示如何,甩给AI试试.

AI结论:在大多数的时候,这两个都是相同的效果.只有在极少数情况下会出现差别.


claude表示:TRACE3(x)会更安全,因为它会拼接多个宏参数于一个字符串.而TRACE2(x)不会拼接字符串.


google Gemini 1.5 Pro表示:

1
printf(TRACE3(hello) TRACE3(world));

TRACE3会连接这两个字符串,而TRACE2不行.


chatgpt4 表示TRACE3会在非法情况下仍然生成空字符串,更加安全.


理论上,我相信了AI给出的解答,但是我仍然编写测试集macro1.c,用于测试所有的情况.

macro1.c代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>
//#x会将操作数转变为字符串字面量
#define TRACE(x) printf("Value of " #x " is %d\n", x)

#define TRACE2(x) #x
#define TRACE3(x) "" #x
int main()
{
int a = 32;
//测试#x确实可以将x转变为字符串
TRACE(a);
printf(TRACE2(sd));
printf(TRACE3(TT3));
//测试2对于x本身就是字符串的处理情况
printf(TRACE2(HELLO));
printf(TRACE2("WORLD"));
printf("\n");
//测试在统一printf函数中连续的两个宏参数连接情况
printf(TRACE2(HELLO) TRACE2(WORLD));
printf(TRACE3(HELLO) TRACE3(WORLD));
printf("\n");
//测试3对于x本身就是字符串的处理情况
printf(TRACE3(HELLO));
printf(TRACE3("WORLD"));
//测试非法x参数
printf(TRACE2());
printf(TRACE3());
return 0;
}

其他的情况,这两者并没有表示出不一样.于是我尝试了一种非法情况:

即使在对于TRACE2和TRACE3传入非法的参数x(x表示没有传入任何东西),理论上:TRACE2显然是没有任何字符串生成,这会导致ERROR,TRACE3会生成一个空字符串,显然TRACE3更安全.

但是在编译的时候,这两者都出现了一样的WARING:

1
2
3
4
5
6
7
8
9
10
11
12
13
macro1.c: In function ‘main’:
macro1.c:26:23: warning: zero-length gnu_printf format string [-Wformat-zero-length]
26 | printf(TRACE2());
| ^
macro1.c:4:20: note: in definition of macro ‘TRACE2
4 | #define TRACE2(x) #x
| ^
macro1.c:6:19: warning: zero-length gnu_printf format string [-Wformat-zero-length]
6 | #define TRACE3(x) "" #x
| ^~
macro1.c:27:16: note: in expansion of macro ‘TRACE3
27 | printf(TRACE3());
|

可能是编译器的优化导致的,于是我传入CFLAGS=-O0取消优化

1
gcc macro1.c CFLAGS=-O0

结果仍然相同.

目前没有好的解释,不过问题不大,好,这个问题过.

但是显然,这个宏的功能很强大.

对于定义一个检测宏是否被定义的宏

Stackoverflow中(上述提到),第一个回答中定义了:

1
2
3
4
5
6
7
8
9
#include <iostream>
#include <cstring>

#define TRACE_STRINGIFY(item) "" #item
#define TRACE(macro, message) \
do { \
if (strcmp("" #macro, TRACE_STRINGIFY(macro))) \
std::cout << message << "\n"; \
} while (0)

对上述代码描述:

如果一个宏被定义了,那么该宏会在TRACE_STRINGIFY宏中被展开,在"" #macro中不会被展开,保持原有的字符信息.

此时strcmp函数返回非0值,执行打印函数,表示该宏被定义.

反之,该宏不会在TRACE_STRINGIFY宏中被展开,而是被转换为原字符串字面值,这与"" #macro的作用一致,此时strcmp函数返回0,则if语句不会执行,打印函数不会被执行,没有任何的输出.

通过描述信息可以看出来,这个宏定义对于这种宏是无效的:

1
#define myself myself

上述是c++版本,我在macro2.c程序中给出c版本

并且给出了另一种优化

macro2.c代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <string.h>
#define TRACE_STRINGIFY(item) "" #item
#define TRACE(macro, error_message, ...) \
do { \
if (!strcmp("" #macro, TRACE_STRINGIFY(macro))) \
fprintf(stderr, "[ERROR]:[TRACE] (%s:%d) " error_message "\n", __FILE__, __LINE__, ##__VA_ARGS__); \
} while (0)

#define dont_know_exist 52
//real_dont_exist
int main()
{

TRACE(dont_know_exist, "This \"%s\" macro don't exist", "dont_know_exist");
TRACE(real_dont_exist, "This \"%s\" macro don't exist", "real_dont_exist");
return 0;
}

在macro2.c代码中,我将TRACE宏定义为:检查某一个宏,如果该宏没有被定义,则打印出错误信息,唯一的逻辑改动就是在strcmp函数前加非

根据上述原理,则可以实现定义一个宏,来检测另一个宏是否被定义,如果没有被定义,则输出错误信息.

打印错误信息的实现,是为TRACE宏添加c语言的变参系统,调用fprintf函数,将错误信息传入stderr,以及使用预定义宏__FILE__,__LINE__来标记错误位置.

添加错误信息的TRACE宏,适用于一些非配置选项类项目构建,用于检测某些需要的宏,如果没有该宏则输出错误信息.而没有添加错误信息的TRACE宏,适用于需要kconfig工具来配合进行需要配置选项类项目的构建,用于检测用户的配置选项,来有选择地编译某些文件或者不编译某些文件.


If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. All the images used in the blog are my original works or AI works, if you want to take it,don't hesitate. Thank you !