为什么semi-colon在宏之后导致非法else编译错误
Why does semi-colon after a macro cause illegal else compile error
显然我对宏的工作原理存在根本性的误解。我认为宏只是导致预处理器用替换文本替换 @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");
之后有一个 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();
你看到 if
和 else
之间有两个语句,这意味着 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
调用缩进到逻辑上属于的位置。这正是空语句(即额外的分号)所在的位置。
显然我对宏的工作原理存在根本性的误解。我认为宏只是导致预处理器用替换文本替换 @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");
之后有一个 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();
你看到 if
和 else
之间有两个语句,这意味着 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
调用缩进到逻辑上属于的位置。这正是空语句(即额外的分号)所在的位置。