在c中,这种模式很常见:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int init_ptr_or_return_err(int *p) {
srand(time(NULL));
// random to make code compile/demonstrate the question
int error = rand() % 2;
if (error) {
return 1;
}
*p = 10;
return 0;
}
int main() {
int a;
init_ptr_or_return_err(&a);
printf("a = %d\n", a);
return 0;
}
在上面的main函数中,不检查函数的返回代码,访问a的值可能在运行时未定义(但这不是静态可确定的)。因此,它通常包含在一个块中,例如:
if (init_ptr_or_return_err(&a)) {
// error handling
} else {
// access a
}
在这种情况下,编译器知道a在else中初始化,因为当且仅当它设置a时,函数返回0。因此,从技术上讲,定义了在else中访问a,但是在if中访问a是未定义的。但是,return 0
可以很容易地“从文件返回一些固定的,但是静态未知的值”(然后在访问之前检查该值)。因此,在任何一种情况下,a是否初始化都不是静态可确定的。
因此,在我看来,在一般情况下,编译器不能静态地判断这是否是未定义的行为,因此不应该能够例如优化它。
这些代码的确切语义是什么(是未定义的行为,还是其他东西,或者静态和运行时未定义行为之间是否存在差异)以及标准在何处指定此内容?如果标准没有定义,我使用的是gcc,所以gcc语境中的答案会有所帮助。
绝大多数未定义的行为不是静态确定的。大多数未定义的行为的形式是“如果达到此语句,并且满足这些条件,则程序具有未定义的行为”。
就是这种情况。当一次调用程序使得rand()
返回奇数时,它具有未定义的行为。当它在rand()
返回偶数时被调用,行为是明确定义的。
此外,编译器可以自由地假设您只在rand()
返回偶数时才调用程序。例如,它可能会优化分支到return 1;
的情况,从而始终打印10。
此代码不一定会调用未定义的行为。读取未初始化的变量总是会调用未定义的行为,这是一个广泛的神话。 UB仅在两种特殊情况下发生:
在主流2的补全平台(x86 / x64,ARM,PowerPC,几乎所有......)上,此代码仅使用未指定的值并调用未指定的行为。这是因为变量的地址已被采用。这将详细解释here。
意味着结果不可靠,但代码将按预期执行,而不会优化香蕉等。
实际上,编译器很可能不会优化函数,但这是因为time()
调用而不是因为某些定义不明确的行为。