你遇到的最糟糕的实际宏/预处理滥用是什么?

176

你所遇到的最糟糕的实际宏/预处理器滥用是什么(请不要回答显然的IOCCC*哈哈*)?

如果有一个有趣的小片段或故事,请添加进来。目标是教授一些东西,而不是总是告诉人们“永远不要使用宏”。


p.s .:我以前使用过宏……但通常我最终会摆脱它们,当我有了“真正的”解决方案时(即使真正的解决方案是内联的,因此类似于宏)。


奖励:给出一个示例,其中宏确实比非宏解决方案更好。

相关问题:C++宏何时有利?


+1 为引起人们对我在宏命令手中遭受的普遍虐待的关注。 - i_am_jorf
37
#define true false //开心调试 :) 该行代码的意思是将 true 宏定义为 false,在进行调试时会出现一些奇怪的结果,因此在代码后面加上注释“happy debugging :)”来缓解调试带来的压力。 - n0rd
社区维基意味着没有人会因为对这个问题或其答案的赞/踩而获得(或失去)声望。许多人认为这样的问题是轻松获取声望的廉价方式,因此如果将其标记为社区维基,人们就不太可能因此感到不满并关闭它。 - Graeme Perrow
2
人们很可能会因为一些幽默有趣的内容而感到不悦并将其关闭:您是在暗示您不希望在Stack Overflow上看到任何幽默/有趣的内容吗? - Trevor Boyd Smith
2
只是一个小观点,预处理器是语言的一部分,因此使用它并不邪恶/错误,就像其他任何东西一样。 - Mr. Boy
显示剩余3条评论
70个回答

1

这不是一个C宏,而是...

很多年前,我有幸负责将原始的《运输大亨》从PC移植到Mac。PC版本完全是用汇编语言编写的,所以我们必须逐行查看整个源代码,并首先将其移植为“PC” C代码,然后再将其移植到Mac上。大部分代码都还好,甚至在某些地方还使用了面向对象的编程。然而,世界渲染系统简直令人难以置信。对于那些没有玩过这款游戏的人来说,世界可以以三种缩放级别之一进行查看。相关代码大致如下:

macro DrawMacro <list of arguments>
   a couple of thousand lines of assembler with loads of conditionals
   based on the macro arguments

DrawZoomLevel1:
   DrawMacro <list of magic numbers>

DrawZoomLevel2:
   DrawMacro <list of more magic numbers>

DrawZoomLevel3:
   DrawMacro <list of even more magic numbers>

我们一定使用了稍旧版本的MASM,因为当我们尝试汇编时,该宏会导致汇编器崩溃。

Skizz


1
尝试调试一个喜欢宏的大项目,有很多调用其他宏的宏,一级接一级等等。 (五到十个宏级别并不罕见)
然后再加上许多 #ifdef 这个宏,#else 那个宏,所以如果你跟随代码,就像一棵树的不同路径。
在大多数情况下,唯一的解决方案是预编译并读取它...

0

我已经将头文件用作大宏:

// compile-time-caller.h
#define param1 ...
#define param2 ...
#include "killer-header.h"

// killer-header.h
// uses param1 and param2

我还创建了递归头文件。

// compile-time-caller.h
#define param1 ...
#define param2 ...
#include "killer-header.h"

// killer-header.h"
#if ... // conditional taking param1 and param2 as parameters
#define temp1 param1
#define temp2 param2
#define param1 ... // expression taking temp1 and temp2 as parameters
#define param2 ... // expression taking temp1 and temp2 as parameters
#include "killer-header.h"
// some actual code
#else
// more actual code
#endif

0

请温柔一点,我写这个是因为这是我能想到的通用捕获异常的唯一方法。

我使用它来捕获并阻止异常从我的公共接口函数中传播出去...

/// Catch all generic exceptions and log appropriately.
/// Logger is insulated from throwing, so this is a NO THROW operation.
#define CatchAll( msg ) \
    catch( const Poco::Exception &e )   \
    {   \
        try{ LogCritical( Logs.System(), std::string( e.displayText() ).append( msg ) );}catch(...){assert(0);} \
    }   \
    catch( const std::exception &e )    \
    {   \
        try{LogCritical( Logs.System(), std::string( e.what() ).append( msg ) );}catch(...){assert(0);} \
    }   \
    catch(...)  \
    {   \
        try{ LogCritical( Logs.System(), std::string( "Exception caught in " __FUNCTION__ ". " ).append( msg ) );}catch(...){assert(0);}    \
    }   

我不喜欢复杂性,也讨厌宏,但除此之外你怎么“做”一个通用的catch处理程序呢?这并不意味着就此结束,这只是我的通用捕获处理程序,用于隔离遗留公共函数并在我知道该函数在可能会抛出C++异常的边界上被调用时快速添加至少一定程度的保护(感谢JNI)。

那么,这会让你惊恐躲藏吗,还是这是唯一可以做到这样的方式?

基本上...

try{
// some block of code capable of throwing
}
CatchAll()

1
在Java中,您可以通过在Exception类上执行catch来执行通用catch。那样就可以捕获所有异常。在您的代码中,我猜测您的异常并不都是从std :: exception派生的? - Trevor Boyd Smith

0
#undef near
#undef far

当我刚开始学习游戏编程时,我在自己写的一个游戏中为相机类编写了一个视锥体,但是我的代码出现了一些奇怪的错误。

后来发现,微软在windows.h中定义了一些关于近和远的#defines,这导致我的_near和_far变量在包含它们的行上出现错误。由于我当时还是个新手,很难追踪问题,因为它们只存在于整个项目的四行中,所以我没有立即意识到。


0

我在libtidy中找到了它:

 /* Internal symbols are prefixed to avoid clashes with other libraries */
 #define TYDYAPPEND(str1,str2) str1##str2
 #define TY_(str) TYDYAPPEND(prvTidy,str)

 TY_(DocParseStream)(bar,foo);

问题在于Visual Studio 2005和其他IDE的“转到定义”和“转到声明”功能只能找到#define TY_(...),而不能找到所需的DocParseStream声明。
也许这样更安全。
我认为他们应该为每个函数添加前缀,而不是调用宏来完成工作。这会使代码变得混乱。但也许我对此有所误解。你怎么看?
附注:似乎几乎所有内部函数const和其他函数都使用了这种前缀。我的同事告诉我这很常见。我错过了什么吗?

0
#define protected private

有时候似乎是个好主意,但如果需要的话,你可能还是应该直接进行字符串替换。protected关键字相当不好,允许内部访问派生类并不比将成员设为public好多少...


0

我不喜欢 Boost 预处理器的东西。虽然我们项目中已经使用了 Boost,但我曾试图弄清楚如何使用它,但据我所知,使用它会使我的错误消息变得非常难以阅读,这并不值得。

我喜欢循环宏的等效想法,但那太过繁琐了。


0

任何使用令牌连接运算符##的宏。

我曾经看到一个同事使用过的宏,他试图自己实现字符串池,所以重新使用了大量宏来实现字符串(当然)并没有正常工作。尝试理解它的功能让我的眼睛因为到处都是##而爆炸。


我经常使用 ##,但我的宏并不特别复杂 - 只是用来生成简单的getter或setter函数的东西。 - Michael Kohne

-1

任何使用Token连接运算符##的东西。我曾经看到过它被用来在C++和其他可怕的事情中拼凑伪模板系统。使用它最糟糕的事情是你的错误消息变得难以理解。

然而,我见过它有一个好用途。有一个宏#MONITOR_COMPONENT(classname),它在编译时生成从预定义监视器类和classname继承并自动注册到单例类的类,该单例类用于监视每个组件。

它有效吗?是的,但这可能不是最好的方法。


在这种情况下,使用宏将类自动注册为单例不是一个坏主意,请参考我的第一个stackoverflow问题:https://dev59.com/PELXs4cB2Jgan1znbtZ5。 - X-Istence
所以我猜你也反对assert()吧?(提示,它通常是一个看起来像你不喜欢的宏) :) - Tim Post
我在第二段中确实说过这是运算符的一个好用法。但我仍然认为这不是解决问题的最佳方案。 - ErgoSum

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