c - libc6 : comma operator in assert macro definition

标签 c macros c-preprocessor

我的系统使用 libc6 2.29。在 /usr/include/assert.h 我可以找到 assert() 宏的定义:

/* The first occurrence of EXPR is not evaluated due to the sizeof,
   but will trigger any pedantic warnings masked by the __extension__
   for the second occurrence.  The ternary operator is required to
   support function pointers and bit fields in this context, and to
   suppress the evaluation of variable length arrays.  */
#  define assert(expr)                          \
  ((void) sizeof ((expr) ? 1 : 0), __extension__ ({         \
      if (expr)                             \
        ; /* empty */                           \
      else                              \
        __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION);   \
    }))

我想知道为什么要使用逗号运算符,以及“由于 sizeof 而未评估 EXPR 的第一次出现”是什么意思。

使用下面的定义会出现什么问题:

#  define assert(expr)                      \
  ({                                        \
      if (expr)                             \
           ; /* empty */                            \
      else                              \
           __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION);    \
    })

编辑:

如果 expr 为真,运算符 ({ }) 会得到什么值?

是否可以重写 assert() 的定义如下?

#  define assert(expr)                          \
  ((void) sizeof ((expr) ? 1 : 0), __extension__ ({         \
      if (!expr)                                \
          __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION); \
    }))

最后一个定义的问题在哪里?

最佳答案

我不是 100% 确定这一点,但我会试一试。

首先,让我们回顾一下这里使用的一些东西。

  • 逗号运算符丢弃前 n-1 个表达式结果并返回第 n 个结果。它通常用作 sequence point ,因为它保证表达式将按顺序计算。

  • 这里使用 __extension__,它是一个 GNU LibC 宏,用于在指定迂腐警告的编译环境下,通过 -ansi-pedantic 等屏蔽 header 中有关 GNU 特定扩展的任何警告。通常在这样的编译器,使用特定于编译器的扩展会抛出警告(如果您在 -Werror 下运行,这很常见,则会引发错误),但由于在使用 GNU 库和编译器的情况下,libc 允许自己使用一些可以安全地进行扩展。

现在,由于实际的断言逻辑可能使用 GNU 扩展(如使用 __extension__ 所示,表达式本身可能已根据其语义(即传递给 assert(expr) 的表达式)引发的任何实际警告将是被屏蔽,因为该表达式在语义上位于 __extension__ block 内,因此被屏蔽。

因此,需要一种方法让编译器有机会显示这些警告,但不评估实际的表达式(因为表达式可能有副作用,并且双重评估可能会导致不良行为)。

您可以使用 sizeof 运算符执行此操作,该运算符接受一个表达式,查看其类型,并找出它占用的字符数 - 而无需实际计算表达式。

例如,如果我们有一个函数 int blow_up_the_world() ,那么表达式 sizeof(blow_up_the_world()) 将计算表达式结果的大小(在本例中为 int ),而无需实际计算表达式。在这种情况下使用 sizeof() 意味着世界实际上不会被炸毁。

但是,如果传递给 exprassert(expr) 包含会触发编译器警告的代码(例如,在 -pedantic-ansi 模式下使用扩展),即使代码在 sizeof() 中,编译器仍会显示这些警告 - 警告否则将被屏蔽在 __extension__ block 内。

接下来,我们看到他们没有将 expr 直接传递给 sizeof ,而是使用三元。那是因为三元的类型是两个结果表达式所具有的任何类型——在本例中是 int 或等价物。这是因为将某些东西传递给 sizeof 会产生一个运行时值——即在 variable length arrays 的情况下——这可能产生不良影响,或者可能产生错误,例如当 passing sizeof a function name 时。

最后,他们想要所有这些,但在实际评估之前,他们希望将 assert() 保留为表达式,因此他们没有使用 do{}while() block 或类似的东西(这最终会导致 assert() 成为语句),而是使用了逗号运算符丢弃第一个 sizeof() 技巧的结果。

关于c - libc6 : comma operator in assert macro definition,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56314110/

相关文章:

c - 在/usr/include/files 周围导航时使用 Vim 查找定义

c - 在 C 中向文件写入结构体/从文件读取结构体

c++ - 使用宏强制预处理器错误

c - 用 C 宏定义常量变量

c - 为什么 Eclipse 生成的头文件以#ifndef 和#define 开头?

c - 将用户输入的字符串传递给需要常量 char* 的函数的问题

c - C 中的 "&&"和 "and"运算符

ruby - 在调用单例方法时动态创建方法作为参数

Java Image 获取桌面图像并创建宏

c++ - 展开 C 或 C++ 源文件中的单个宏