具有 2 个参数问题的 C 预处理器宏
C Preprocessor Macro with 2 arguments issue
我的 C 代码中有这个宏:
#define ASSERT(ret, num) \ // make sure ret === num
if (ret != num) { \
fprintf(stderr, "Error [%d] at line [%d] in function [%s]. Date: [%s] Time: [%s]\n", \
ret, __LINE__, __FUNCTION__, __DATE__, __TIME__); \
exit(ret); \
}
然后我这样称呼它(所有参数都是整数):
ASSERT(errNo, MPI_SUCCESS);
ASSERT(aliveNeighbors, 8);
ASSERT(newGrid, !NULL);
我收到类似 (GCC v5.4) 的错误:
expected identifier or ‘(’ before ‘if’
if (ret != num) { \
error: stray ‘\’ in program
ASSERT(errNo, MPI_SUCCESS);
error: stray ‘\’ in program
ASSERT(aliveNeighbors, 8);
stray ‘\’ in program
ASSERT(newGrid, !NULL);
这里有什么问题?
给定宏定义:
#define ASSERT(ret, num) \ // make sure ret === num
if (ret != num) { \
fprintf(stderr, "Error [%d] at line [%d] in function [%s]. Date: [%s] Time: [%s]\n", \
ret, __LINE__, __FUNCTION__, __DATE__, __TIME__); \
exit(ret); \
}
您遇到了很多问题,其中许多问题在您的错误消息中可见。
反斜杠-换行符拼接发生在标准(ISO/IEC 9899:2011)§5.1.1.2 中定义的处理的第 2 阶段翻译阶段:
Physical source file multibyte characters are mapped, in an implementation-defined manner, to the source character set (introducing new-line characters for end-of-line indicators) if necessary. Trigraph sequences are replaced by corresponding single-character internal representations.
Each instance of a backslash character (\
) immediately followed by a new-line character is deleted, splicing physical source lines to form logical source lines. Only the last backslash on any physical source line shall be eligible for being part of such a splice. A source file that is not empty shall end in a new-line character, which shall not be immediately preceded by a backslash character before any such splicing takes place.
The source file is decomposed into preprocessing tokens7) and sequences of white-space characters (including comments). A source file shall not end in a partial preprocessing token or in a partial comment. Each comment is replaced by one space character. New-line characters are retained. Whether each nonempty sequence of white-space characters other than new-line is retained or replaced by one space character is implementation-defined.
Preprocessing directives are executed, macro invocations are expanded, and _Pragma
unary operator expressions are executed. If a character sequence that matches the syntax of a universal character name is produced by token
concatenation (6.10.3.3), the behavior is undefined. A #include
preprocessing directive causes the named header or source file to be processed from phase 1 through phase 4, recursively. All preprocessing directives are then deleted.
(第 5-8 阶段完成处理,但与本次讨论无关。)
请注意,注释在第 3 阶段被删除,预处理发生在第 4 阶段,在标记化输入上。
您的宏定义的第一行是:
#define ASSERT(ret, num) \ // make sure ret === num
这将宏的主体定义为 \
。不能在继续宏的反斜杠后放置注释,也不能在宏正文中使用 // … <eol>
样式的注释。如果您在注释后添加反斜杠,它将继续注释到下一行,并且宏主体仍然是空的。可以谨慎使用/* … */
条评论:
#define ASSERT(ret, num) /* make sure ret === num */ \
这会正常工作。因为原始宏有尾随 // … <eol>
注释,所以完成了宏。第一条错误消息是:
expected identifier or ‘(’ before ‘if’
if (ret != num) { \
这是因为 'macro definition' 中的 if
行实际上不是宏的一部分,并且 if
在它出现的上下文中不应该出现。
其他三个错误基本相同:
error: stray ‘\’ in program
ASSERT(errNo, MPI_SUCCESS);
宏扩展为一个反斜杠,但在有效的 C 程序文本中不能有杂散的反斜杠,因此出现错误消息。当出现反斜杠时,它们总是在程式化的上下文中,后面跟着特定的其他字符(数字、某些字母、某些标点符号、换行符)。
宏的主体也有问题:
__DATE__
和__TIME__
是代码编译(预处理)时的日期和时间,不是代码运行时的日期和时间。它们很少有用,在这种情况下也没有用。如果您想要程序 运行 时的日期和时间信息,您将定义并调用一个函数来确定和格式化当前 date/time.
__FUNCTION__
不标准; __func__
是标准的预定义标识符。
正如所写,宏不能在某些上下文中安全使用,例如:
if (alpha == omega)
ASSERT(retcode, 0);
else if (gamma == delta)
gastronomic_delight(retcode, gamma, alpha);
else
子句错误地与(半更正的)ASSERT
中的 if
相关联,而不是与 alpha == omega
测试相关联。
将这些更改组合成一个可行的解决方案:
#define ASSERT(ret, num) \
do { \
if ((ret) != (num)) err_report(ret, __LINE__, __func__); \
} while (0)
你有:
extern _Noreturn void err_report(int err, int line, const char *func);
此函数格式化消息并报告标准错误,并在适当时确定时间和日期。它还退出(因此 _Noreturn
属性 — C11 的一个特性。您可能更喜欢 #include <stdnoreturn.h>
并使用 noreturn
而不是 _Noreturn
。您可能会也可能不会决定添加__FILE__
作为传递给函数的值;它需要另一个参数。
还要注意调用:
ASSERT(newGrid, !NULL);
有嫌疑。它转换为 if ((newGrid) == (!0))
这不是您想要的。您必须更加努力地测试非空指针:
ASSERT(newGrid != NULL, 1);
这会将newGrid
中的值与NULL
进行比较,如果值为非空则生成1
,如果为空则生成0
,即与 1
参数相比。请注意,此测试的错误编号没有帮助 — 它将是 0
.
如果我在评论中遗漏了任何关键点,请告诉我。
我的 C 代码中有这个宏:
#define ASSERT(ret, num) \ // make sure ret === num
if (ret != num) { \
fprintf(stderr, "Error [%d] at line [%d] in function [%s]. Date: [%s] Time: [%s]\n", \
ret, __LINE__, __FUNCTION__, __DATE__, __TIME__); \
exit(ret); \
}
然后我这样称呼它(所有参数都是整数):
ASSERT(errNo, MPI_SUCCESS);
ASSERT(aliveNeighbors, 8);
ASSERT(newGrid, !NULL);
我收到类似 (GCC v5.4) 的错误:
expected identifier or ‘(’ before ‘if’
if (ret != num) { \
error: stray ‘\’ in program
ASSERT(errNo, MPI_SUCCESS);
error: stray ‘\’ in program
ASSERT(aliveNeighbors, 8);
stray ‘\’ in program
ASSERT(newGrid, !NULL);
这里有什么问题?
给定宏定义:
#define ASSERT(ret, num) \ // make sure ret === num
if (ret != num) { \
fprintf(stderr, "Error [%d] at line [%d] in function [%s]. Date: [%s] Time: [%s]\n", \
ret, __LINE__, __FUNCTION__, __DATE__, __TIME__); \
exit(ret); \
}
您遇到了很多问题,其中许多问题在您的错误消息中可见。
反斜杠-换行符拼接发生在标准(ISO/IEC 9899:2011)§5.1.1.2 中定义的处理的第 2 阶段翻译阶段:
Physical source file multibyte characters are mapped, in an implementation-defined manner, to the source character set (introducing new-line characters for end-of-line indicators) if necessary. Trigraph sequences are replaced by corresponding single-character internal representations.
Each instance of a backslash character (
\
) immediately followed by a new-line character is deleted, splicing physical source lines to form logical source lines. Only the last backslash on any physical source line shall be eligible for being part of such a splice. A source file that is not empty shall end in a new-line character, which shall not be immediately preceded by a backslash character before any such splicing takes place.The source file is decomposed into preprocessing tokens7) and sequences of white-space characters (including comments). A source file shall not end in a partial preprocessing token or in a partial comment. Each comment is replaced by one space character. New-line characters are retained. Whether each nonempty sequence of white-space characters other than new-line is retained or replaced by one space character is implementation-defined.
Preprocessing directives are executed, macro invocations are expanded, and
_Pragma
unary operator expressions are executed. If a character sequence that matches the syntax of a universal character name is produced by token concatenation (6.10.3.3), the behavior is undefined. A#include
preprocessing directive causes the named header or source file to be processed from phase 1 through phase 4, recursively. All preprocessing directives are then deleted.
(第 5-8 阶段完成处理,但与本次讨论无关。)
请注意,注释在第 3 阶段被删除,预处理发生在第 4 阶段,在标记化输入上。
您的宏定义的第一行是:
#define ASSERT(ret, num) \ // make sure ret === num
这将宏的主体定义为 \
。不能在继续宏的反斜杠后放置注释,也不能在宏正文中使用 // … <eol>
样式的注释。如果您在注释后添加反斜杠,它将继续注释到下一行,并且宏主体仍然是空的。可以谨慎使用/* … */
条评论:
#define ASSERT(ret, num) /* make sure ret === num */ \
这会正常工作。因为原始宏有尾随 // … <eol>
注释,所以完成了宏。第一条错误消息是:
expected identifier or ‘(’ before ‘if’
if (ret != num) { \
这是因为 'macro definition' 中的 if
行实际上不是宏的一部分,并且 if
在它出现的上下文中不应该出现。
其他三个错误基本相同:
error: stray ‘\’ in program
ASSERT(errNo, MPI_SUCCESS);
宏扩展为一个反斜杠,但在有效的 C 程序文本中不能有杂散的反斜杠,因此出现错误消息。当出现反斜杠时,它们总是在程式化的上下文中,后面跟着特定的其他字符(数字、某些字母、某些标点符号、换行符)。
宏的主体也有问题:
__DATE__
和__TIME__
是代码编译(预处理)时的日期和时间,不是代码运行时的日期和时间。它们很少有用,在这种情况下也没有用。如果您想要程序 运行 时的日期和时间信息,您将定义并调用一个函数来确定和格式化当前 date/time.__FUNCTION__
不标准;__func__
是标准的预定义标识符。正如所写,宏不能在某些上下文中安全使用,例如:
if (alpha == omega) ASSERT(retcode, 0); else if (gamma == delta) gastronomic_delight(retcode, gamma, alpha);
else
子句错误地与(半更正的)ASSERT
中的if
相关联,而不是与alpha == omega
测试相关联。
将这些更改组合成一个可行的解决方案:
#define ASSERT(ret, num) \
do { \
if ((ret) != (num)) err_report(ret, __LINE__, __func__); \
} while (0)
你有:
extern _Noreturn void err_report(int err, int line, const char *func);
此函数格式化消息并报告标准错误,并在适当时确定时间和日期。它还退出(因此 _Noreturn
属性 — C11 的一个特性。您可能更喜欢 #include <stdnoreturn.h>
并使用 noreturn
而不是 _Noreturn
。您可能会也可能不会决定添加__FILE__
作为传递给函数的值;它需要另一个参数。
还要注意调用:
ASSERT(newGrid, !NULL);
有嫌疑。它转换为 if ((newGrid) == (!0))
这不是您想要的。您必须更加努力地测试非空指针:
ASSERT(newGrid != NULL, 1);
这会将newGrid
中的值与NULL
进行比较,如果值为非空则生成1
,如果为空则生成0
,即与 1
参数相比。请注意,此测试的错误编号没有帮助 — 它将是 0
.
如果我在评论中遗漏了任何关键点,请告诉我。