具有 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 阶段翻译阶段:

  1. 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.

  2. 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.

  3. 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.

  4. 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.

如果我在评论中遗漏了任何关键点,请告诉我。