libc6:断言宏定义中的逗号运算符
Posted
技术标签:
【中文标题】libc6:断言宏定义中的逗号运算符【英文标题】:libc6: comma operator in assert macro definition 【发布时间】:2019-05-26 13:52:02 【问题描述】:我的系统使用 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); \
))
我想知道为什么要使用逗号运算符,以及“The first occurrence of EXPR is not evaluated due to the sizeof
”是什么意思。
使用以下定义会有什么问题:
# 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); \
))
最后一个定义的问题在哪里?
【问题讨论】:
【参考方案1】:我不是 100% 确定这一点,但我会试一试。
首先,让我们回顾一下这里使用的一些东西。
逗号运算符丢弃前 n-1 个表达式结果并返回第 n 个结果。它通常用作sequence point,因为它保证表达式将按顺序计算。
这里使用__extension__
,它是一个GNU LibC 宏,用于屏蔽在编译环境下通过-ansi
或@987654326 指定迂腐警告的头文件中有关GNU 特定扩展的任何警告@ 等。通常在此类编译器下,使用特定于编译器的扩展会引发警告(如果您在 -Werror
下运行,则会引发错误,这很常见),但因为在 GNU 库和编译器正在运行的情况下使用时,libc 允许自己使用一些可以安全使用的扩展。
现在,由于实际的断言逻辑可能使用 GNU 扩展(正如使用 __extension__
所表明的那样,根据其语义,表达式本身可能引发的任何真正警告(即传递给 @987654329 的表达式) @) 将被屏蔽,因为该表达式将在语义上位于 __extension__
块内并因此被屏蔽。
因此,需要有一种方法让编译器有机会显示这些警告,但不计算实际表达式(因为表达式可能有副作用,双重计算可能会导致不良行为)。
您可以通过使用 sizeof
运算符来做到这一点,该运算符接受一个表达式,查看它的类型,并找到它占用的字符数 - 而无需实际评估表达式。
例如,如果我们有一个函数int blow_up_the_world()
,那么表达式sizeof(blow_up_the_world())
将在不实际计算表达式的情况下找到表达式结果的大小(在本例中为int
)。在这种情况下使用sizeof()
意味着世界实际上不会被炸毁。
但是,如果传递给 assert(expr)
的 expr
包含会触发编译器警告的代码(例如,使用 -pedantic
或 -ansi
模式下的扩展),编译器仍会显示这些警告,即使代码位于 sizeof()
内 - 否则将在 __extension__
块内隐藏的警告。
接下来,我们看到他们没有将expr
直接传递给sizeof
,而是使用三元组。这是因为三元的类型是两个结果表达式所具有的任何类型 - 在这种情况下是 int
或等效的东西。这是因为将某些内容传递给 sizeof
将导致 runtime 值 - 即在 variable length arrays 的情况下 - 可能产生不良影响,或者可能产生错误,比如passing sizeof
a function name时。
最后,他们想要所有这些,但在实际评估之前,并希望将 assert()
保留为表达式,因此不要使用 dowhile()
块或类似的东西,这最终会导致 assert()
成为声明,他们改为使用逗号运算符丢弃第一个 sizeof()
技巧的结果。
【讨论】:
如果我理解正确,由于使用了逗号操作符,操作符 ( ) 是必要的,对吧?需要列表中的最后一个元素是可以计算为值的表达式。但是当 expr 为真时评估的值是多少?我已经编辑了我的问题以获取更多详细信息()
没有什么特别之处,除了 __extension__
是一个类似函数的标识符(如宏函数,即#define __extension__(...) ...
),它需要一个块。我不确定当表达式为真时您的意思是什么 - 返回值是 __extension__
返回的任何值。我不确定assert()
的返回值是否指定,甚至是否可用。【参考方案2】:
(
不是标准 C,会在标准 C 编译模式下触发警告或错误。
所以他们正在使用__extension__
,这将禁用任何此类诊断。
但是,__extension__
也会屏蔽 expr
中的非标准构造,您确实希望对其进行诊断。
这就是为什么他们需要 expr
重复两次,一次在 __extension__
内部,一次在外部。
但是expr
只需要评估一次。
因此他们通过将另一个出现的expr
放在sizeof
中来抑制另一个评估。
只是sizeof(expr)
是不够的,因为它不适用于函数名称之类的东西。
所以改用sizeof((expr) ? 1 : 0)
,就没有这个问题了。
所以生成的表达式的两部分是 (a) sizeof((expr) ? 1 : 0)
和 (b) __extension__(...)
部分。
只有在expr
出现问题时才需要第一部分进行诊断。
第二部分执行实际的断言。
最后用逗号将两部分连接起来。
【讨论】:
以上是关于libc6:断言宏定义中的逗号运算符的主要内容,如果未能解决你的问题,请参考以下文章