我的系统使用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); \
}))
最后一个定义在哪里出现问题?
答案 0 :(得分:4)
我不确定100%,但我会尝试一下。
首先,让我们回顾一下这里使用的一些东西。
逗号运算符丢弃前n-1个表达式结果,并返回第n个结果。通常将其用作sequence point,因为可以保证表达式将按顺序求值。
此处使用__extension__
(这是一个GNU LibC宏),用于通过{{1}来掩盖在指定脚警告的编译环境下有关标头中特定于GNU扩展的任何警告。 }或-ansi
等。通常在此类编译器下,使用特定于编译器的扩展名将引发警告(如果在-pedantic
下运行,则会出错,这很常见),但是由于在使用GNU库和编译器的情况下,libc允许自己使用一些可以安全使用的扩展。
现在,由于实际的断言逻辑可能使用了GNU扩展名(如使用-Werror
所示,因此表达式本身可能已经提出了任何真正的警告,因为给出了其语义(即,将表达式传递给了__extension__
)将被屏蔽,因为该表达式在语义上位于assert(expr)
块内,因此被屏蔽。
因此,需要有一种方法使编译器有机会显示这些警告,但无需评估实际的表达式(因为该表达式可能会产生副作用,并且双重评估可能会导致不良行为)。
您可以使用__extension__
运算符来执行此操作,该运算符可以获取表达式,查看其类型并查找其占用的字符数-无需实际评估表达式。
例如,如果我们有一个函数sizeof
,则表达式int blow_up_the_world()
将找到表达式结果的大小(在本例中为sizeof(blow_up_the_world())
),而无需实际评估表达。在这种情况下,使用int
意味着世界不会被炸毁。
但是,如果传递给sizeof()
的{{1}}包含可能触发编译器警告的代码(例如,在expr
或assert(expr)
模式下使用扩展名),则编译器即使代码位于-pedantic
内,仍会显示这些警告-否则这些警告会在-ansi
块内被屏蔽。
接下来,我们看到它们不是使用sizeof()
直接传递给__extension__
而是使用三元数。这是因为三元数的类型是两个结果表达式都具有的类型-在这种情况下为expr
或类似的东西。这是因为将某些内容传递给sizeof
会导致 runtime 值-即在variable length arrays的情况下-可能会产生不良影响,或可能会产生错误,例如passing sizeof
a function name。
最后,他们想要所有这些,但是在实际评估之前,他们想要保留int
作为表达式,因此不要使用sizeof
块或类似的东西,最终会导致{ {1}}是一条语句,他们改用逗号运算符丢弃第一个assert()
技巧的结果。
答案 1 :(得分:3)
({
不是标准C,它将在标准C编译模式下触发警告或错误。 __extension__
,它将禁用任何此类诊断。__extension__
还将掩盖您确实要诊断的expr
中的非标准构造。expr
重复两次,一次在__extension__
内部,一次在外部。expr
仅需要评估一次。expr
放在sizeof
内,从而抑制了另一次评估。sizeof(expr)
还不够,因为它不适用于函数名之类的东西。sizeof((expr) ? 1 : 0)
来代替,这没有这个问题。 sizeof((expr) ? 1 : 0)
和(b)__extension__(...)
部分。expr
出问题时才需要进行诊断的第一部分。