'else' 在定义带有参数的宏时没有先前的 'if' 错误

'else' without previous 'if' error when defining macro with arguments

考虑下面的 C 代码。

#include <stdio.h>
#define foo(x) if (x>0) printf("Ouch\n");

int main()
{
    int a = 4;
    int b = -3;

    if(a>b)
        foo(b) ;
    else
        printf("Arrg!\n");
    printf("thanks\n");

    return 0;
}

当程序为 运行 时,我收到一条错误消息 error: ‘else’ without a previous ‘if’。当我们根据宏定义将foo(b)替换为if (b>0) printf("Ouch\n")时,用大括号写的程序不应该转成下面的代码吗?

if(a>b){
    if(x>0){
        printf("Ouch\n");
    }
}
else{
    printf("Arrh!\n");
}
printf("thanks\n");    

我不明白为什么编译器会抱怨。程序实际转移到什么地方?

谢谢

不,它正在变成这个代码:

if (a > b)
        if (b > 0) printf("Ouch\n");;
    else
        printf("Arrg!\n");

注意("Ouch\n")后有两个个分号。这就是破坏代码的原因。 else 跟在第二个分号之后,它是一个空语句,而不是之前的 if 语句。原因是您的宏定义中有一个分号,而您调用宏的地方有另一个分号。

我建议将类函数宏的语句放在它自己的块中,如 所建议的那样。但这是一般性建议,在这个具体示例中,最好根本没有宏。

ifelse 块之后使用 { } 以避免此类问题一直被认为是安全和良好的做法。

正确的是

if(a>b) { 
    foo(b) ; /* keep inside { } */
}
else {
    printf("Arrg!\n");
}

shouldn't the program transfer into the following code when written with braces?

不,编译器不会手动放置大括号。宏替换后 gcc -E test.c 看起来像

int main()
{
    int a = 4;
    int b = -3;

    if(a>b)
        if (b>0) printf("Ouch\n"); ; /* extra semicolon causes the issue */
    else /* there is no if for this else block, previous one terminated by extra ; in above if */
        printf("Arrg!\n");
    printf("thanks\n");

    return 0;
}

示例代码:

#include <stdio.h>
#define foo(x) if ((x)>0) printf("Ouch\n") /* if condition was wrong.. instead of x use (x) */
int main(void)
{
    int a = 4;
    int b = -3;
    if(a>b)
    { /* always keep curly braces even though there is only one statement after if */
        foo(b);
    }
    else
    {
        printf("Arrg!\n");
    }
    printf("thanks\n");
    return 0;
}

尽管我建议像下面这样定义 MACRO

#define foo(x) \
    do {                   \
        if((x) > 0)         \
        printf("Ouch\n");  \
    } while(0)

我的建议是忽略任何关于如何修复那个宏的建议,真正的问题是你正在使用这个宏完全没有。

几乎没有在现代语言中使用宏的理由(实际上只是稍微不那么愚蠢的文本替换) C 用于条件编译以外的任何东西。

  • 值类型宏通常应替换为枚举,因为它们可以更好地保留类型信息。
  • 函数类型宏应该 函数,因为现代编译器在需要时可以很容易地使它们内联。

使用实函数还可以解决所有这些奇怪的边缘情况,例如:

#define calc(x) x * x
:
int a = 7;
int b = calc(a + 1);   // a + (1 * a) + 1, NOT (a + 1) * (a + 1)

如果你想让它可靠地工作,你需要宏类似于 ((x) * (x)),但即使 that 也会因更复杂而失败,例如 b = calc(a++).


所以,简而言之,您应该在您的代码中包含的内容是:

void foo(int x) {
    if (x > 0)
        printf("Ouch\n");
}

如果你需要能够使用函数宏任何地方(裸语句,花括号if块中的语句,语句在一个无支撑的 if 块中,有支撑和无支撑的 while 语句等等),你必须求助于奇怪的宏,例如(当然要确保 #include <stdbool.h> 才能访问 false):

#define XYZZY(s) do {plugh(s);} while (false)

但是,我强烈建议首先让它们发挥作用。

问题是您的宏扩展为

if(a>b)
    if (b>0) printf("Ouch\n"); ; 
else 
    printf("Arrg!\n");
//...

并且由于额外的 ; 而无法与 else 一起使用。 (您也可以在 x 参数 (#define foo(x) if ((x)>0) printf("Ouch\n")) 中加上括号)。

如果您丢失了宏中的 ;,您将得到不同的解析:else 将与内部的 if 匹配,如下所示:

if(a>b){ /*braces inserted to show the interpretation*/
    if (b>0) printf("Ouch\n");
    else printf("Arrg!\n");
}

虽然您可以通过使宏悬空 else

来解决问题
#define foo(x) if ((x)>0) printf("Ouch\n"); else
//a ; after the macro else would complete the `else` with an empty branch

使用它,尤其是在其他 if-else 中,在编译 -Wall 和类似选项时会在 gcc/clang 等编译器中触发警告,所以最好的处理方法是使用惯用语

#define macro() do{/*macro_body*/}while(0)

你的情况

#define foo(x) do{ if ((x)>0) printf("Ouch\n"); }while(0)

有些人喜欢总是将复合语句(由 { } 包围)与 if/else 语句一起使用,虽然这也可以解决问题,我觉得如果你做一个类似函数的宏给别人用,最好不要强行给他们一个样式。