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

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

9
一位曾经是程序员的“技术经理”在我们的C++项目中引入了以下精妙的宏,因为他认为在DOM解析例程中检查NULL值太麻烦了:
TRYSEGV
CATCHSEGV

在底层,这些使用了setjmplongjmp和一个信号处理程序来模拟“捕获”段错误的能力。
当然,代码中没有重置跳转指针,一旦代码退出了原始TRYSEGV宏调用的作用域,任何段错误都会返回到(现在无效的)jump_env指针。
代码会立即在那里崩溃,但在此之前会破坏程序堆栈,使得调试基本上毫无意义。

不确定宏是不是最大的问题,但这太可怕了。 - reuben

8
AI游戏编程智慧一书中有一章节使用宏来创建有限状态机的脚本语言。由于该书和代码受版权保护,因此这里提供一个Google图书链接,其中详细介绍了宏(生成的脚本语言可在第324页找到)。

3
为什么这样很邪恶?宏(macros)在某种程度上简化了代码。好的编程语言能够让你创建与问题领域相似的迷你语言。除非你想要详细说明可移植汇编,否则C语言提供了预处理器作为主要选项。 - Pontus Gagge
它本身并不邪恶。滥用预处理器让它执行本不应该执行的任务才是问题所在。 - MrValdez
9
@MrValdez:你的辱骂是别人的杰作。 :) - MikeyB

7

我喜欢这个例子,它使用宏来逼近圆周率的值。圆的大小越大,逼近的精度就越高。

#define _ -F<00||--F-OO--;
int F=00,OO=00;main(){F_OO();printf("%1.3f\n",4.*-F/OO/OO);}F_OO()
{
            _-_-_-_
       _-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
        _-_-_-_-_-_-_-_
            _-_-_-_
}

另一个是 C 程序

c

为了进行编译,您需要将c定义为:
-Dc="#include <stdio.h> int main() { char *t =\"Hello World\n\"; while(*t) putc(*t++, stdout); return 0; }"

7
在Lucent,我曾经查看过Steve Bourne原始Unix shell的源代码,并发现他使用C预处理器使C看起来像Pascal或Algol。处理if语句的部分看起来像这样:
#define IF   if (
#define THEN ) {
#define ELSE } else {
#define ELIF } else if (
#define FI   ; }

我的一个朋友告诉我,他在1990年代中期对其进行了一些维护,但它仍然是原来的样子。(这里有一个教训,即代码库固有的保守性。)
当然,史蒂夫在早期进行了这个实验,如果他后来写了它,我相信他会三思而后行。
更新:根据维基百科的 Bourne Shell文章,宏使它具有了Algol 68 的风格。完整的宏集在这里!它们显然影响了国际混淆C代码大赛的创始人们。

2
这更像Algol而不是Pascal - 它是使用反向关键字(如'fi')来标记结构的结束的Algol。Shell通常使用它。有趣的问题:为什么Bourne shell中的循环结尾标记为“done”而不是“od”? - Jonathan Leffler
3
因为“od”代表八进制转储,这是Unix第七版中的一种实用工具。 - kmarsh
这在“else if”上失败了,Algol有“else if”吗? - smerlin
这个链接对我无效,但是这个可以用:http://minnie.tuhs.org/cgi-bin/utree.pl?file=V7/usr/src/cmd/sh/mac.h - luser droog
我曾经看到一些在法国官方学院任教的教授向学生分发一个名为"francais.h"的文件,其中包含类似于#define si if#define tant_que while这样的内容,以便让他们“用法语编程”。幸运的是,这门课从未尝试过涵盖STL。如果他们让学生写std::chaine_de_caracteres而不是std::string,我会崩溃的。 - André Caron
显示剩余3条评论

6

C语言中的协程(又称无栈线程):) 这是一种邪恶的技巧。

#define crBegin static int state=0; switch(state) { case 0:
#define crReturn(i,x) do { state=i; return x; case i:; } while (0)
#define crFinish }
int function(void) {
    static int i;
    crBegin;
    for (i = 0; i < 10; i++)
        crReturn(1, i);
    crFinish;
}

int decompressor(void) {
    static int c, len;
    crBegin;
    while (1) {
        c = getchar();
        if (c == EOF)
            break;
        if (c == 0xFF) {
            len = getchar();
            c = getchar();
            while (len--)
            crReturn(c);
        } else
        crReturn(c);
    }
    crReturn(EOF);
    crFinish;
}


void parser(int c) {
    crBegin;
    while (1) {
        /* first char already in c */
        if (c == EOF)
            break;
        if (isalpha(c)) {
            do {
                add_to_token(c);
        crReturn( );
            } while (isalpha(c));
            got_token(WORD);
        }
        add_to_token(c);
        got_token(PUNCT);
    crReturn( );
    }
    crFinish;
}

5
switch (device_id)
{
#ifndef PROD_1
#ifndef PROD_2
#ifdef PROD_3
  case ID_1:
#endif

#ifdef PROD_4

#ifdef PROD_5
  case ID_2:
  case ID_3:
  case ID_4:
#elif defined(PROD_4)
#ifndef PROD_6
  case ID_1:
#endif // PROD_6
  case ID_5:
#endif

  case ID_6:
#endif

#ifdef PROD_7
  #ifndef PROD_8
    case ID_7:
  #endif
#endif

(为保护当事人隐私,以下涉及的名字均为化名)

注意,我们甚至还没有接触任何代码,这只是为了进入第一个实际的代码片段。对于几个函数而言,情况实际上类似(但不完全相同),最终每个函数只有4种可能的变体(这些变体基本上都是复制/粘贴并带有轻微的差异和自己的#ifdefs)。


那真是太美妙了。但是为了什么目的呢? - luser droog
如果我能在这里输入一个笑脸就好了! - luser droog

4
#define FLASE FALSE

这位程序员是一个打字不好的人,而这是他最常见的错误。


我知道有些开发人员在命令行环境中使用别名“mroe”(更多)等,但在代码中这样做是最棒的。 - Curt Nichols
10
大家都会犯错! - Konamiman

4
#define interface struct

在我需要使用的Watcom/Powersoft IDE Optima++ 的一些头文件中。

1
Windows的其中一个头文件也将“interface”定义为某个东西。 - bk1e
+1 这个错误让我犯了难,但是我的一位(更有经验的)同事找到了它的问题所在。 - John

4

我要从记忆中翻译,大概是这样的: 使用写Symbian应用程序的库。在一个必须包含的头文件中,隐藏着这个小宝石:

// Here come the register defines:
#define C <something>
#define N <something>
<two more single letter defines>

我们的代码加载一个硬编码文件名的文件失败了。当我们将文件位置从C盘改为D盘时,它神奇地工作了...

3
#define unless(cond) if(!cond)
#define until(cond) while(!cond)

使用:

unless( ptr == NULL) 
    ptr->foo();

6
甚至不安全:除非(a + b == c)所做的不是你想要的! - Jonathan Leffler
6
更安全的做法是修改为: #define unless(cond) if(! (cond)) #define until(cond) while(! (cond)) - Joel
2
那实际上是来自Perl的,所以我可以看出作者的意图。实际上并不邪恶(忽略乔纳森的评论,这很容易修复),但相当易读(我不能说同样适用于语言的其余部分)。 - new123456

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