C预处理器:在#warning中展开宏

52

我想在 #warning 指令中打印宏值(展开宏)。

例如,对于以下代码:

#define AAA 17
#warning AAA = ???

期望的编译时输出应该是

warning: AAA = 17

我应该使用什么来完成???,或者说,我应该如何修改代码?


3
根据1999年的C标准,你只能使用#error来处理这样的情况,它不会扩展任何宏,只是字面上打印文本并停止编译。你到底想用这个做什么?我能帮你实现什么目的? - Alexey Frunze
我有许多层次的makefile,根据make目标参数以不同的方式定义AAA。我想验证目标的定义是否正确。而且我不想创建#if AAA = 1 ... #warning "is 1"的列表。 - elomage
此外,这是针对嵌入式系统的,没有显示器,因此我无法通过添加类似于printf(#AAA)的内容并在运行时检查它来轻松测试宏值。 - elomage
所以你要这样写:#if A == 1\#error A = 1\#elif A == 2\#error A = 2\#endif - Alexey Frunze
1
@AlexeyFrunze 这正是我想要避免的 - 请看我的上面的评论。我可能不知道所有可能的值,或者可能有太多这样的值。 - elomage
可能是重复的问题,参考如何在编译时显示 #define 的值? - moooeeeep
5个回答

63
你可以使用预处理指令#pragma message
示例:
#define STR_HELPER(x) #x
#define STR(x) STR_HELPER(x)

#define AAA 123
#pragma message "content of AAA: " STR(AAA)

int main() { return 0; }

输出可能如下所示:

$ gcc test.c
test.c:5:9: note: #pragma message: content of AAA: 123
 #pragma message("content of AAA: " STR(AAA))
         ^

供参考:


3
请务必使用 #pragma message 而不是 #message#warning - Fantastory
1
这是一个很好的开端...但值得注意的是,它打印出宏的扩展...而不是值。如果您有一个宏,递归地扩展到冗长(但简单)的数学表达式,可能涉及其他宏,那么这可能对您没有太大帮助。 - Cheetah
如果#pragma message可以生成警告就太好了。 #pragma GCC diagnostic warning "message "不允许它(https://gcc.gnu.org/onlinedocs/gcc/Diagnostic-Pragmas.html)。 - pevik
如果您尝试扩展的宏具有多个参数,则需要以下答案的变体:#define STR_HELPER(...) #__VA_ARGS__``#define STR(...) STR_HELPER(__VA_ARGS__) - Cameron Tacklind
@Cheetah 有没有一种方法可以计算并打印出实际值? - endolith
显示剩余2条评论

9
如果你真的想发出警告,下面的方法也可以。但是这取决于C99是否启用(适用于gcc 4.8.2或更高版本,未在早期版本上测试):
#define N 77

#define __STRINGIFY(TEXT) #TEXT
#define __WARNING(TEXT) __STRINGIFY(GCC warning TEXT)
#define WARNING(VALUE) __WARNING(__STRINGIFY(N = VALUE))

#if N == 77
_Pragma (WARNING(N))
#endif

7
我不建议使用 #warning,因为它不是标准的 C 语言。此外,你需要警告但又不想出现错误的情况真的有吗?警告通常是编译器在你进行可疑或非常危险的操作时使用的,但这种操作是符合 C 标准的,正常应用中并不存在这种情况,你要么希望代码完美无误地编译通过,要么根本不想让其编译通过。因此,我会使用标准的 #error,而不是非标准的 #warning。
你不能输入预处理器定义的实际内容。类似以下内容可能已足够:
#if (AAA < SOMETHING) && (AAA > SOMETHING_ELSE)
  #error AAA is bad.
#endif

我认为对于程序员来说,这已经足够详细了。但是,如果你真的想要更多细节并且你有一个现代的C编译器,你可以使用static_assert。然后你就可以实现接近你想要的效果:

#include <assert.h>

#define xstr(s) str(s)
#define str(s) #s
#define err_msg(x) #x " is " xstr(x)

#define AAA 17

static_assert(AAA != 17, err_msg(AAA));

这个宏混乱应该会打印 AAA 是 17。关于这些宏的工作原理的解释可以在这里找到。

我不确定 static_assert 是否包含在 C99 或 C11 中,但它肯定在 C11 中。您可能需要使用一些 GCC 扩展来启用它。


请参考此问题,了解C语言中的静态断言:https://dev59.com/qXA75IYBdhLWcg3wRWyc - moooeeeep
我只需要一个带有值的信息提示来确认我的怀疑,即特定情况下全局内部参数不正确。这里没有“坏”值。结果证明它是不正确的。但感谢 static_assert,它可以在 g++ 版本 4.3(使用选项 -std=c++0x)之后工作,而 gcc 版本 4.6 则将 static_assert 别名为 _Static_assert 而无需选项。 - elomage
@elomage _Static_assert 现在是 C 语言的关键字,自 C11 起。 <assert.h> 包含一个宏 static_assert,该宏扩展为 _Static_assert。由于两者都是标准的,因此使用任何一种形式都应该没问题。 - Lundin
为什么要这样做呢?因为如果我正在更改我使用的MCU的核心支持库中的某些内容,以进行调试(例如将HAL_SYSTEM_RESET()宏从实际控制寄存器写入重新定义为“while(1)”以便我可以使用调试器检查状态),那么我希望确保该构建永远不会到达实际环境。我仍然需要它构建以使测试工作。当然,只有在生产构建中没有警告的情况下才有效... - Jostikas
1
@Jostikas 版本控制真的非常有用,也是您所描述问题的正确解决方案。只需将修改后的代码放入用于实验的分支即可。 - Lundin
返回的文本:是的。我仍然希望附带一个大红色详细信息:即使没有其他东西,它也作为非常明显的文档,记录了在所述实验分支中预计破坏的功能以及这些破坏发生的位置。 - Jostikas

2

很多时候我会让我的Makefile生成一个名为generated.h的本地文件,其中包含所需的定义。

generated.h:  Makefile
        echo >generated.h "// 警告:生成的文件。请改变Makefile"
        date >>generated.h '+//   生成于 %Y-%m-%d %H:%M:%S'
        echo >>generated.h "#if AAA == AAA_bad"
        echo >>generated.h "#warning \"AAA = $(AAA_bad)\""
        echo >>generated.h "#endif"

需要使用#include "generated.h"来引用它。

当然,您可以在此处添加任何复杂性,但如果行数超过几行,则可能需要将复杂性放入单独的脚本中,因为混乱的Makefile可能会成为可怕的维护问题。稍加想象,您就可以从少量输入中生成大量测试。

将generated.h目标依赖于Makefile是至关重要的,以确保如果目标中的指令发生更改,则重新制作generated.h。如果您有一个单独的generated.sh脚本,那么它也将在依赖项列表中。

免责声明:未进行真实测试。


0

另一种简单的方法是,特别是当您处理Makefile项目(如linux、u-boot、qemu等)时,您可以查看文件的预处理结果。
例如,

要查看arch/arm64/kernel/head.S的预处理文件,
您可以执行make arch/arm64/kernel/head.s。(小写s)。

要查看foo/bar/baz.c的预处理文件,
您可以执行make foo/bar/baz.i

所有宏都会扩展到最终值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接