在本文中,下面是一段可以触发除零的代码示例:
if (arg2 == 0)
ereport(ERROR, (errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
/* No overflow is possible */
PG_RETURN_INT32((int32) arg1 / arg2);
ereport
这是一个宏,它扩展为对可能返回也可能不返回的bool
-returning 函数的调用,并且在其返回值上有条件(使用 a )调用另一个函数。在这种情况下,我相信水平会无条件地导致其他地方。errstart
?:
ereport
ERROR
longjmp()
因此,对上述代码的一个天真的解释是,如果arg2
为非零,则进行除法并返回结果,而如果arg2
为零,则报告错误并且不进行除法。但是,链接的论文声称 C 编译器可能会在零检查之前合法地提升除法,然后推断永远不会触发零检查。他们唯一的推理,在我看来是不正确的,是
[T] 程序员未能通知编译器对 ereport(ERROR, : : :) 的调用没有返回。这意味着该除法将始终执行。
John Regehr 有一个更简单的例子:
void bar (void);
int a;
void foo3 (unsigned y, unsigned z)
{
bar();
a = y%z;
}
根据这篇博文,clang 将模运算提升到对 的调用之上bar
,他展示了一些汇编代码来证明这一点。
我对适用于这些片段的 C 的理解是
不返回或可能不返回的函数在标准 C 中是格式良好的,并且此类声明不需要特定的属性、铃声或口哨。
对不返回或可能不返回的函数的调用的语义是明确定义的,特别是 C99 中的 6.5.2.2“函数调用”。
由于
ereport
调用是一个完整的表达式,因此在;
. 同样,由于bar
John Regehr 代码中的调用是一个完整的表达式,因此在;
.因此在
ereport
调用或bar
调用与除法或模数之间存在一个序列点。C 编译器可能不会将未定义行为引入自己不会引发未定义行为的程序。
这五点似乎足以得出结论,上述除零测试是正确编写的,并且将模提升到调用之上bar
是不正确的。两个编译器和许多专家不同意。我的推理有什么问题?