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

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个回答

2

任何使用sendmail及其魔法配置语法的内容


是的,那很糟糕。m4的语法不太好。我曾经深入研究过它的文档,想着可以用它来做一些其他的东西,但发现作者没有包含循环结构,因为你可以使用递归来构建一个自己的循环结构。之后我就再也没有用m4做任何事情了。 - Michael Kohne

2
#define "CR_LF" '\r'

那让我很困惑了一段时间!


1
当时把一个宏作为参数传递给另一个宏似乎是个不错的主意。(我简直受不了在多个地方定义数值列表的想法。)这里的代码有些牵强(也不是很有动力),但能让你明白我的意思:
#define ENUM_COLORS(CallbackMacro) \
    CallbackMacro(RED)   \
    CallbackMacro(GREEN) \
    CallbackMacro(BLUE)  \
    // ...

#define DEFINE_COLOR_TYPE_CALLBACK(Color) \
    Color,

enum MyColorType {
    ENUM_COLORS(DEFINE_COLOR_TYPE_CALLBACK)
};

void RegisterAllKnownColors(void)
{
#define REGISTER_COLOR_CALLBACK(Color) \
    RegisterColor(Color, #Color);

    ENUM_COLORS(REGISTER_COLOR_CALLBACK)
}

void RegisterColor(MyColorType Color, char *ColorName)
{
    // ...
}

最后两行 #define 中缺少反斜杠? - Jonathan Leffler
好的。添加了缺失的反斜杠。 - reuben

1

我同意大部分情况下,宏是很难使用的,但我发现有一些情况下它们非常有用。

在我看来,这个实际上非常出色,因为你只能通过sprintf获得类似的东西,然后需要资源分配等等,而且所有工作都完全由预处理器完成。

// Macro: Stringize
//
//      Converts the parameter into a string
//
#define Stringize( L )          #L


// Macro: MakeString
//
//      Converts the contents of a macro into a string
//
#define MakeString( L )     Stringize(L)


// Macro: $LINE
//
//      Gets the line number as a string
//
#define $LINE                   MakeString( __LINE__ )


// Macro: $FILE_POS
//
//      Gets the current file name and current line number in a format the Visual Studio
//      can interpret and output goto
//
// NOTE: For VS to properly interpret this, it must be at the start of the line (can only have whitespace before)
//
#define $FILE_POS               __FILE__ "(" $LINE ") : "

我不太喜欢使用另一种方法,但我发现它非常有用。就是像这样做,基本上允许我快速生成具有可变数量模板参数的模板。
#define TEMPLATE_DEFS    typename ReturnType
#define TEMPLATE_DECL   ReturnType
#define FUNCTION_PARAMS void
#define FUNCTION_PASS   
#define GENERIC_CALLBACK_DECL_NAME      CallbackSafePointer0
#include "Callback.inl"

#define TEMPLATE_DEFS   typename ReturnType, typename P1
#define TEMPLATE_DECL   ReturnType, P1
#define FUNCTION_PARAMS P1 param1
#define FUNCTION_PASS   param1
#define GENERIC_CALLBACK_DECL_NAME      CallbackSafePointer1
#include "Callback.inl"

#define TEMPLATE_DEFS   typename ReturnType, typename P1, typename P2
#define TEMPLATE_DECL   ReturnType, P1, P2
#define FUNCTION_PARAMS P1 param1, P2 param2
#define FUNCTION_PASS   param1, param2
#define GENERIC_CALLBACK_DECL_NAME      CallbackSafePointer2
#include "Callback.inl"

#define TEMPLATE_DEFS   typename ReturnType, typename P1, typename P2, typename P3
#define TEMPLATE_DECL   ReturnType, P1, P2, P3
#define FUNCTION_PARAMS P1 param1, P2 param2, P3 param3
#define FUNCTION_PASS   param1, param2, param3
#define GENERIC_CALLBACK_DECL_NAME      CallbackSafePointer3
#include "Callback.inl"

// and so on...

虽然这使得阅读"Callback.inl"有点可怕,但它完全消除了使用不同数量参数重写相同代码的需要。我还应该提到,"Callback.inl"在文件末尾#undefs所有宏,因此,这些宏本身不会干扰任何其他代码,只是使"Callback.inl"写起来有点困难(不过阅读和调试并不太难)


1

我大约10年前使用的ASIC的驱动程序代码有很多部分看起来像:

int foo(state_t *state) {
    int a, b, rval;

    $
    if (state->thing == whatever) {
        $
        do_whatever(state);
    }
    // more code

    $
    return rval;
}

在经过很多思考之后,我们终于找到了定义:
#if DEBUG
#define $ dolog("%s %d", __FILE__, __LINE__);
#else
#define $
#endif

这很难找到,因为使用它的源文件中没有任何包含文件。有一个名为top.c的源文件看起来像:
#include <namechanged.h>
#include <foo.c>
#include <bar.c>
#include <baz.c>

果然,这是Makefile中唯一被引用的文件。每当你更改任何内容时,你都必须重新编译所有内容。这是为了“使代码更快”。


1

另一种“创造性”的预处理器使用方式,尽管它更多地涉及术语的使用而不是机制(这些机制非常平凡):

/***********************************************************************
 * OS2 and PCDOS share a lot of common codes.  However, sometimes
 * OS2 needs codes similar to those of UNIX.  NOTPCDOS is used in these
 * situations
 */

#ifdef OS2
#define PCDOS
#define NOTPCDOS
#else /* OS2 */
#ifndef PCDOS
#define NOTPCDOS
#endif /* PCDOS */
#endif /* OS2 */

真正的代码 - 我以为我已经删除了它,但显然没有。我可能在某个临时分支中这样做了,并且没有得到将其检查回主要代码的权限。这是待办事项清单上的另一项。


1

在声明中发现了太多混淆:

NON_ZERO_BYTE         Fixed(8)  Constant('79'X),

发现后来:
IF WORK_AREA(INDEX) = ZERO_BYTE THEN  /* found zero byte */ 
   WORK_AREA(INDEX) = NON_ZERO_BYTE ; /* reset to nonzero*/

1

最糟糕的滥用(我有时也会犯这种错误)是将预处理器用作某种数据文件替换,例如:

#define FOO_RELATION \  
BAR_TUPLE( A, B, C) \  
BAR_TUPLE( X, Y, Z) \ 

然后在其他地方:

#define BAR_TUPLE( p1, p2, p3) if( p1 ) p2 = p3;
FOO_RELATION
#undef BAR_TUPLE

这将导致:

if( A ) B = C;
if( X ) Y = Z;

这个模式可以用来做各种(可怕的)事情...生成switch语句或巨大的if else块,或者与“真实”代码进行接口。你甚至可以使用它来::咳嗽::在非面向对象的上下文菜单系统中生成一个上下文菜单::咳嗽::。不过我从来不会做这么无聊的事情。

编辑:修复了括号不匹配的问题并扩展了示例


GCC实际上在内部使用了这种风格。适度使用很有用,但一定要确保文档记录完整。 - Jeff M
括号不匹配是有意为之的吗? - Jonathan Leffler

1

1
我曾经看过一个宏包,它会将每个 C 关键字别名化,让您可以有效地使用克林贡语进行编程。没错,克林贡语。不幸的是,该项目已经在几年前被放弃并删除了。

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