我正在阅读有关未定义行为的内容,我不确定它是否只是一个编译时功能,或者它是否可以在执行时发生.
我很好理解这个例子(这是从维基百科的未定义行为页面中提取的):
C语言的一个例子:
int foo(unsigned x) { int value = 5; value += x; if (value < 5) bar(); return value; }值
x
不能为负,并且由于有符号整数溢出是C中未定义的行为,编译器可以假设在if检查行value >= 5
.因此编译器可以忽略if
和调用该函数bar
,因为if
它没有副作用,并且它的条件永远不会被满足.因此,上面的代码在语义上等同于:int foo(unsigned x) { int value = 5; value += x; return value; }
但这发生在编译时.
如果我写,例如:
void foo(int x) { if (x + 150 < 5) bar(); } int main() { int x; std::cin >> x; foo(x); }
然后用户键入MAX_INT - 100
("2147483547",如果32位整数).
会有一个整数溢出,但是AFAIK,它是CPU 的算术逻辑单元会产生溢出,所以这里不涉及编译器.
它仍然是未定义的行为吗?
如果是,编译器如何检测溢出?
我能想到的最好的是CPU的溢出标志.如果是这种情况,是否意味着如果在执行时随时设置CPU的溢出标志,编译器可以执行他想要的任何操作?
是的,但不一定是我认为你可能意味着它的方式,也就是说,如果在机器代码中有一个添加,并且在运行时添加包装(或以其他方式溢出,但在大多数架构上它将包装)不是UB通过它自己.UB完全属于C(或C++)领域.这个添加可能是添加无符号整数或者是编译器可以进行的某种优化,因为它知道目标平台的语义,并且可以安全地使用依赖包装的优化(但你不能,除非你当然用无符号做类型).
当然,这根本不意味着使用"仅在运行时换行"的构造是安全的,因为这些代码路径在编译时也会中毒.例如,在您的示例中,
extern void bar(void); void foo(int x) { if (x + 150 < 5) bar(); }
由GCC 6.3编译,目标是x64
foo: cmp edi, -145 jl .L4 ret .L4: jmp bar
这相当于
void foo(int x) { if (x < -145) bar(); // with tail call optimization }
..如果你假设有符号整数溢出是不可能的,那就是相同的(在某种意义上它会在输入上放置一个隐含的前提条件,使得溢出不会发生).