【问题标题】:Why does semi-colon after a macro cause illegal else compile error为什么宏后的分号会导致非法else编译错误
【发布时间】:2016-01-18 12:24:46
【问题描述】:

显然我对宏的工作原理存在根本性的误解。我认为一个宏只是导致预处理器用替换文本替换 @defined 宏。但显然情况并非总是如此。我的代码如下: TstBasInc.h

#pragma once

#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdarg.h>
#include <time.h>

//  copy macro
#define Cpy(ToVar, FrmVar) do {                                                                                 \
                                errno = strncpy_s(ToVar, sizeof(ToVar), FrmVar, _TRUNCATE);                     \
                                    if (errno == STRUNCATE)                                                     \
                                        fprintf(stderr, "string '%s' was truncated to '%s'\n", FrmVar, ToVar);  \
                            } while(0);

//  clear numeric array macro
#define ClrNumArr(ArrNam, ArrCnt)           \
            for (s = 0; s < ArrCnt; s++)    \
                ArrNam[s] = 0;


uint32_t    s;          //  subscript

typedef struct {
    short   C;
    short   YY;
    short   MM;
    short   DD;
} SysDat;

TstMacCmpErr:

#include "stdafx.h"
#include "TstBasInc.h"      //  test basic include file

#define ARRCNT 3

int main()
{
    char    Cnd = 'E';      //  define to use 'else' path
    char    ToVar[7 + 1];   //  Cpy To-Variable
    int     IntArr[ARRCNT]; //  integer array

    Cpy(ToVar, "short")                     //  compiles with or without the semi-colon
    if (Cnd != 'E')
//      Cpy(ToVar, "short string");         //  won't compile:  illegal else without matching if
        Cpy(ToVar, "short string")          //  will compile
    else
        Cpy(ToVar, "extra long string");    //  compiles with or without the semi-colon
//  the following code shows how I thought the macro would expand to    
    {                                                                                   \
        errno = strncpy_s(ToVar, sizeof(ToVar), "short str", _TRUNCATE);                \
        if (errno == STRUNCATE)                                                         \
            fprintf(stderr, "string '%s' was truncated to '%s'\n", "short str", ToVar);;    \
    }

    if (Cnd == 'E') {
        ClrNumArr(IntArr, ARRCNT)               //  compiles with or without the semi-colon
            printf("intarr[0] = %d\n", IntArr[0]);
    }
    else
        printf("intarr[0] is garbage\n");

    return 0;
}

结果如下:

string 'extra long string' was truncated to 'extra l'
string 'short str' was truncated to 'short s'
intarr[0] = 0;

正如 cmets 所说,当我在 Cpy(ToVar, "short string"); 之后有一个分号时,它甚至无法编译,因为我收到了 "C2181 illegal else without matching if" 错误。如您所见,我尝试按照in this post 的建议在宏中添加一个do-while,但这没有任何区别。当直接复制宏代码时(即使没有 do-while),该代码也可以正常工作。我原以为只需在宏中添加大括号就可以解决问题,但事实并非如此。它必须与以if 结尾的Cpy 有关,因为ClrNumArr 宏在编译时带有或不带有分号。那么有人能告诉我为什么Cpy 宏不只是替换文本吗?我一定错过了一些简单的东西。

我正在使用 VS 2015 社区版更新 1。

编辑:我记录了问题并将其隔离到(我认为)Cpy 宏中的if 语句。 仍然没有人解释为什么宏没有按照我认为的方式扩展。那应该是帖子的标题,因为这是我的问题,而不是分号的问题,我现在有一个解决方案。

【问题讨论】:

  • 不要在宏定义中使用分号。
  • 不要在函数可以工作的地方使用宏。
  • gcc.gnu.org/onlinedocs/cpp/Swallowing-the-Semicolon.html 注意分号实际上是一个空语句
  • 0) 如前所述,不要在函数也可以工作的地方使用宏。一个原因只是咬你。 1) 不要尝试过早的优化。编译器和 CPU 可能比你聪明。 2) 如果您的代码确实证明太慢,请使用inline 函数和/或链接时间优化 (LTO)。 3)作为一个初学者,甚至不要考虑这些事情。
  • 不要使用 K&R。这甚至不包括 C99,更不用说标准 C(即 C11 - 没有其他有效的 C 标准)。您也不想学习驾驶/使用 Model-T 汽车或前数字手机。

标签: c macros


【解决方案1】:

如果你放分号,

    Cpy(ToVar, "short")                     //  compiles with or without the semi-colon
    if (Cnd != 'E')
//      Cpy(ToVar, "short string");         //  won't compile:  illegal else without matching if
        Cpy(ToVar, "short string")          //  will compile
    else
        Cpy(ToVar, "extra long string");    //  compiles with or without the semi-colon

这使得语法看起来像

if (...)
    ...;
    ;                // <-- The problem here
else
   ...;

这是非法语法,因为else 在此语法中没有关联的if

OTOH,以防你写

if (...) {
    ...;
    ;                // <-- no problem here, inside a block
 }
else
   ...;

else 与之前的 if 相关联。所以,一切都好。

【讨论】:

  • 谢谢。我已经发现,如果我将宏括在大括号中,它会正常工作。这就是我在宏中添加大括号的原因,这样每次我想在if 中使用宏时,我都不必继续输入额外的大括号。我的问题是为什么宏中的大括号不能正确展开。
  • @J.Toran 我认为你没有明白我的帖子的重点。宏定义是一个语句,如果代码中没有{},则结尾的; 会破坏if..else 联盟。您是否在 MACRO 定义中使用 {} 并不重要。我说清楚了吗?
  • 我想我明白你在说什么。但是为什么下面的代码有效:` { \ errno = strncpy_s(ToVar, sizeof(ToVar), "short str", _TRUNCATE); \ if (errno == STRUNCATE) \ fprintf(stderr, "string '%s' was truncated to '%s'\n", "short str", ToVar);; \ }` 并且宏没有扩展到此代码?
  • 谢谢。直到我阅读了 dbush 的帖子,我才明白你的帖子的重点。你和其他人的正确答案值得称赞,但我做不到。
【解决方案2】:

如果您查看扩展时发生的情况,问题就会变得很明显。宏已经扩展为包含尾随分号的语句。

如果您将它与 if-else 构造一起使用,问题就会变得很明显,因为如果您附加一个额外的分号,它会扩展为两个语句。

例如,如果您执行以下操作:

#define HELLO printf("Hello\n");

然后

if( some_condition )
    HELLO;
else
    something_else();

这将扩展为

if( some_condition )
    printf("Hello\n");;
else
    something_else();

您会看到ifelse 之间有两个语句,这意味着else 变得不合适了。

使用do - while(0) 的真正原因是要创建适合要安装一个语句的位置,然后必须省略while(0) 后面的分号。那就是你的定义应该是这样的:

#define Cpy(ToVar, FrmVar) do   {                                                                                 \
                            errno = strncpy_s(ToVar, sizeof(ToVar), FrmVar, _TRUNCATE);                     \
                                if (errno == STRUNCATE)                                                     \
                                    fprintf(stderr, "string '%s' was truncated to '%s'\n", FrmVar, ToVar);  \
                        } while(0)

【讨论】:

  • 谢谢。但是为什么大括号不与其他行一起扩展到do { strncpy(etc) if (condition) printf(line) } while (0);;?最后一个“;”不只是一个空操作吗?还是不行?
  • 是的,最后一个分号就是所谓的空语句。在解析源代码时,它本身就是一个声明。
  • 当我在复制宏代码的部分末尾添加; 时,如:fprintf(stderr, "string '%s' was truncated to '%s'\n", "short str", ToVar);; 和 2 个分号,它编译并运行正常。我的问题是为什么宏没有用大括号和 do-while 展开?
  • 感谢您的回答。当我暂时离开;,并将其添加到宏的“调用”之后,它编译并运行正常。但是我仍然不确定为什么宏没有像我在问题中显示的那样扩展。我直接从宏复制的代码工作正常。额外的部分似乎与我认为宏将扩展到的部分相同。 (如果你明白我的意思。)
  • 谢谢。我不明白宏没有像我想象的那样扩展,直到我阅读了 dbush 的帖子。你帮助我理解也值得称赞。
【解决方案3】:

在对 Sourav 的回答的评论中,您问为什么它可以用作宏体:

{ 
    errno = strncpy_s(ToVar, sizeof(ToVar), "short str", _TRUNCATE); 
    if (errno == STRUNCATE) 
        fprintf(stderr, "string '%s' was truncated to '%s'\n", "short str", ToVar);; 
}

这是因为在您的上下文中,它的计算结果如下:

if (Cnd != 'E')
{ 
    errno = strncpy_s(ToVar, sizeof(ToVar), "short str", _TRUNCATE); 
    if (errno == STRUNCATE) 
        fprintf(stderr, "string '%s' was truncated to '%s'\n", "short str", ToVar);; 
}
else
    Cpy(ToVar, "extra long string");    //  compiles with or without the semi-colon

块的if 部分,以前是单个语句,现在是一个块。所以现在else 已正确匹配。

但是,这仍然不是实现宏的好方法。然后包含分号(宏的用户通常希望使用分号)会导致错误。

if (Cnd != 'E')
    Cpy(ToVar, "short string");     // invalid syntax
else
    Cpy(ToVar, "extra long string");

你一直在说“为什么宏没有像我想象的那样扩展”。它正在按照您的预期扩展。根据您的原始定义,这是:

if (Cnd != 'E')
    Cpy(ToVar, "short string");
else
    Cpy(ToVar, "extra long string");    //  compiles with or without the semi-colon

扩展为:

if (Cnd != 'E')
    do{
        errno = strncpy_s(ToVar, sizeof(ToVar), "short string", _TRUNCATE);
        if (errno == STRUNCATE)
            fprintf(stderr, "string '%s' was truncated to '%s'\n", "short string", ToVar);
    } while(0);;
else
    Cpy(ToVar, "extra long string");    //  compiles with or without the semi-colon

while(0)(宏的一部分)之后的第一个分号结束了if 语句的第一部分。第二个分号(不是宏的一部分)是另一个(空)语句。此语句被视为在if 语句之外。因此,else 放错了位置。

如果你有这样的事情,你会得到同样的错误:

if (Cnd != 'E')
    do{
        errno = strncpy_s(ToVar, sizeof(ToVar), "short string", _TRUNCATE);
        if (errno == STRUNCATE)
            fprintf(stderr, "string '%s' was truncated to '%s'\n", "short string", ToVar);
    } while(0);
fprintf(stderr, "this is outside of the if statement\n");
else
    Cpy(ToVar, "extra long string");    //  compiles with or without the semi-colon

请注意,我将fprintf 调用缩进到逻辑上属于的地方。这正是空语句(即额外的分号)所属的位置。

【讨论】:

  • 啊...谢谢。我现在看到您将宏扩展为 } while(0);; 的位置。当我在脑海中扩展宏时,我将多余的分号放在printf 后面而不是while 后面。这也解释了为什么 skyking 的回答奏效了。
猜你喜欢
  • 1970-01-01
  • 2011-06-09
  • 1970-01-01
  • 1970-01-01
  • 2012-05-14
  • 1970-01-01
  • 2019-08-30
  • 2014-05-23
  • 1970-01-01
相关资源
最近更新 更多