为什么semi-colon在宏之后导致非法else编译错误

Why does semi-colon after a macro cause illegal else compile error

显然我对宏的工作原理存在根本性的误解。我认为宏只是导致预处理器用替换文本替换 @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;

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

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

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

如果你加了分号,

    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-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)

在对 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");

你一直在说"why didn't the macro expand the way I thought it would"。它如您所想的那样扩展。根据你原来的定义,这个:

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 调用缩进到逻辑上属于的位置。这正是空语句(即额外的分号)所在的位置。