【问题标题】:'else' without previous 'if' error when defining macro with arguments使用参数定义宏时,没有先前的“if”错误的“else”
【发布时间】:2019-05-01 08:29:09
【问题描述】:

考虑下面的 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&gt;0) printf("Ouch\n")时,程序用大括号写不应该转成下面的代码吗?

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

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

谢谢

【问题讨论】:

标签: c nested-if


【解决方案1】:

不,它正在变成这样的代码:

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

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

我建议将类似函数的宏的语句放在它自己的块中,就像this answer 建议的那样。但这是一个普遍的建议,在这个具体示例中,最好根本没有宏。

【讨论】:

  • 从技术上讲,替换为if (b &gt; 0) printf("Ouch\n");;。此外,您没有告诉 OP 如何解决此问题。删除尾随的;,他可能会推断会导致另一个问题,更难发现。
  • @chqrlie 哦,哇,我不敢相信我错过了。谢谢指正,已经修复了。
【解决方案2】:

问题是你的宏扩展为

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

这不适用于else,因为有额外的;。 (您也可以用括号括起来 x 参数 (#define foo(x) if ((x)&gt;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 语句,虽然这也可以解决问题,但我觉得如果你为其他人使用,最好不要强加样式。

【讨论】:

    【解决方案3】:

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

    几乎没有理由在现代 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)),但即使 也会因更复杂而失败,例如 b = calc(a++)。


    所以,简而言之,您的代码中应该是:

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

    如果您确实需要能够在任何地方使用函数宏(裸语句、带括号的 if 块中的语句、未带括号的 if 块中的语句、带括号的和无支撑的 while 语句等),您必须求助于奇怪的宏,例如(确保#include &lt;stdbool.h&gt; 可以访问false,当然):

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

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

    【讨论】:

    • 我遇到过一两种情况,其中函数式宏是最好的解决方案,但这种情况非常罕见。如果您使用的是宏,是时候进行代码审查了。
    • 关于如何修复宏的建议确实教会了 OP 关于宏陷阱和陷阱。您的建议非常好,除了使用 false 如果 OP 不包含 &lt;stdbool.h&gt; 可能未定义@
    【解决方案4】:

    在 if 和 else 块之后使用 { } 始终被认为是安全且良好的做法,以避免此类问题。

    正确的是

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

    程序写的时候不应该转成下面的代码吗 带大括号?

    不,编译器不会手动放置花括号。宏替换后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)
    

    【讨论】:

    • 这个建议解决了症状,而不是真正的问题。 #define foo(x) if (x&gt;0) printf("Ouch\n"); 以多种方式损坏,花括号不会使 OP 在 foo(b &amp; 1) 上失败
    猜你喜欢
    • 2021-12-06
    • 2020-02-10
    • 2022-11-14
    • 2015-09-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-10
    相关资源
    最近更新 更多