有没有用于测试扩展C/C++ #define 宏的实用工具?

78

我似乎经常花费过多的时间尝试让 #define 宏完全符合我的意愿。我会在下面发布我目前的困境,并感激任何帮助。但更重要的问题是,是否有任何实用程序可以推荐,能够快速显示宏实际上正在做什么?如果我能看到出了什么问题,即使是缓慢的试错过程也会快得多。

目前,我正在从一个 DLL 中动态加载一长串函数。我设置的方式是,函数指针与导出函数具有相同的名称,而用于原型定义的 typedef(s) 具有相同的名称,但是有一个前置下划线。因此,我想使用 #define 简化对长长的函数指针列表的赋值。

例如,在以下代码语句中,“hexdump”是 typedef'd 函数点的名称,也是函数的名称,而“_hexdump”是 typedef 的名称。如果 GetProcAddress() 失败,则增加失败计数器。

if (!(hexdump = (_hexdump)GetProcAddress(h, "hexdump"))) --iFail;

那么假设我想用宏来替换上述每一行,就像这样...

GETADDR_FOR(hexdump )

这是我到目前为止想出的最好方案。它并不能正常工作(我的 // 注释只是为了防止消息中的文本格式化)...

// #define GETADDR_FOR(a) if (!(a = (#_#a)GetProcAddress(h, "/""#a"/""))) --iFail; 

再次强调,虽然我很希望知道我犯了什么愚蠢的错误,但如果有一个工具能够通过简单地插入我的宏来显示我的错误,那将使我的一天。


1
你使用什么编辑器? - Anycorn
点赞,但希望它是一个集成开发环境;-) - Mawg says reinstate Monica
5个回答

127

图片描述访问https://godbolt.org/。在左侧窗格中输入您的代码,并选择编译器为gcc,将参数设置为-E放在右侧窗格中。您的预处理代码将出现在右侧。


4
好工具!应该是最佳答案,因为它需要的工作量最少。 - David
1
对于 MSVC(Visual C++),将 /E 设置为编译器选项,然后打开输出面板(在底部的选项卡中)。您还可以将此输出面板拖动到页面的任何位置。 - user1350534
另一个选择是 https://repl.it。只需启动一个新的 repl 来选择语言并将代码粘贴到左侧编辑器中。按 F1 并键入或查找 "shell" 以打开 shell,然后键入 gcc -E main.c(假设 main.c 是文件名),预处理的代码将被显示。 - Floella
1
哦,我的波塞冬!我在考虑使用Godbolt的网站,但是没有想到我可以获得预处理的输出。显然只需输入标志!有时候我感觉我14岁的时候更聪明... - SO_fix_the_vote_sorting_bug

34
你可以直接将你的代码通过预处理器运行,它会展示代码被展开后的结果(或者在必要时输出错误):
$ cat a.c
#define GETADDR_FOR(a) if (!(a = (#_#a)GetProcAddress(h, "/""#a"/""))) 
GETADDR_FOR(hexdump)

$ gcc -E a.c
# 1 "a.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "a.c"
a.c:1:36: error: '#' is not followed by a macro parameter

GETADDR_FOR(hexdump)
在GCC中,使用gcc -E foo.c只进行预处理。
Visual Studio使用/P参数。

1
谢谢。我想我会研究一下GCC。我想我希望有一个东西可以“尽可能地展开”我的宏,以便我可以在视觉上看到它的效果。但是,就像错误的C代码一样,如果我的宏是完全垃圾的,预处理器可能根本无法展开它。;-) 我想我只需要阅读更好的文档。但仍然很不错,有一个可以转储成功扩展结果的工具,因为比错误消息更糟糕的是,扩展可以正常工作,但不会生成预期的代码! GCC / P会这样做吗? - Randy
2
@Randy:实际上,Jerry Coffin推荐的Boost Wave具有“替换跟踪”功能,它输出一个文件,显示宏替换过程中所采取的步骤。由于通常会有比我们想象中更多的步骤,因此它非常冗长,但对于复杂或长的宏定义特别有用。 - James McNellis
@Randy:我认为,如果你的宏需要一个复杂的调试工具来进行调试,那么你可能做错了什么。 :-) - Omnifarious
1
再说,几乎我们作为程序员的所有人类努力都始于一个简单前提,但最终都会把奥卡姆剃刀的所有可能解释全部推翻。;-) - Randy
1
有没有什么方法可以省略这个 std 库的引用?会产生很多不必要的噪音。 - River Tam

6

用户在哪里提到了Visual Studio? - Andrew Barber
2
@AndrewBarber 他在评论中提到他访问了MSDN。 - xshoppyx
2
链接无效 - jalal sadeghi

5

您似乎对C预处理器宏中字符串化或标记粘贴的确切语法感到困惑。

您可能会发现这个关于C预处理器宏的页面有所帮助。

特别地,我认为这个宏应该像这样编写:

#define GETADDR_FOR(a) if (!(a = (_##a)GetProcAddress(h, #a))) --iFail

结尾的;应该被跳过,因为你很可能会将其输入为GETADDR_FOR(hexdump);,如果不这样做,它在你的C代码中将显得非常奇怪,并且会混淆许多语法高亮器。
正如其他人提到的那样,gcc -E将运行预处理器并跳过其他编译步骤。这对于调试预处理器问题非常有用。

啊,谢谢!我对字符串处理器#的正确使用有了更清晰的概念。这是关于它的用法和连接用法!非常感谢!还有,谢谢您提供文章链接。看起来比我在MSDN上找到的简短笔记要全面得多! - Randy

3
您可能想看一下Boost Wave。像大多数Boost库一样,它更像是一个库而不是实用程序,但它确实有一个驱动程序作为完整的预处理器。

1
我不知道为什么这个被踩了... Wave确实有一个预处理器实现(虽然它并不完整;它不支持几个宏替换的晦涩方面)。 - James McNellis
@James:是的,“complete”可能不是很准确,我只是想说它有驱动代码,所以你可以将其编译和链接为一个完整的可执行程序。如果(例如)你想要宏替换*而不包括文件包含,则这是我知道的最好的起点(并且你经常不想要文件包含,因为它会产生大量的扫描内容,以找到你关心的内容)。 - Jerry Coffin
也许你可以提一下 wave 驱动程序的 跟踪能力?因为我不知道有其他工具可以逐步展示宏的扩展过程。gdb 未来可能会加入逐步扩展功能(如此处所述:https://dev59.com/lrXna4cB1Zd3GeqPP77F)... - T S

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