为什么宏后的分号会导致非法的其他编译错误

时间:2016-01-18 12:24:47

标签: c macros

显然,我对宏如何运作有一个根本的误解。我认为宏只是导致预处理器用替换文本替换@define d宏。但显然情况并非如此。我的代码如下: 的 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");之后我有一个分号时,它甚至无法编译,因为我收到"C2181 illegal else without matching if"错误。如您所见,我尝试按照建议in this post在宏中添加do-while,但它并没有任何区别。直接复制宏代码(即使没有do-while),代码工作正常。我原本以为只是在宏中添加大括号就可以解决问题,但事实并非如此。它必须与Cpy结尾的if有关,因为ClrNumArr宏可以使用或不使用分号进行编译。那么有人可以告诉我为什么Cpy宏不能替换文本吗?我必须遗漏一些简单的东西。

我正在使用VS 2015 Community Edition Update 1.

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

3 个答案:

答案 0 :(得分:3)

如果你加了分号,

    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相关联。所以,一切都很好。

答案 1 :(得分:3)

如果你看一下扩展会发生什么,问题就变得很明显了。宏已经扩展到包含尾随分号的语句。

如果你将它与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)的原因是创建一个适合一个语句适合的位置,然后必须在{{1}之后省略后面的分号}}。那就是你的定义应该是这样的:

while(0)

答案 2 :(得分:1)

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