over/underflow 是执行时未定义的行为吗?

Is over/underflow an undefined behavior at execution time?

我正在阅读有关未定义行为的信息,我不确定它是否仅是编译时功能,或者它是否可以在执行时发生。

这个例子我理解的很好(摘自Undefined Behavior page of Wikipedia):

An example for the C language:

int foo(unsigned x)
{
    int value = 5;
    value += x;
    if (value < 5)
        bar();
    return value;
}

The value of x cannot be negative and, given that signed integer overflow is undefined behavior in C, the compiler can assume that at the line of the if check value >= 5. Thus the if and the call to the function bar can be ignored by the compiler since the if has no side effects and its condition will never be satisfied. The code above is therefore semantically equivalent to:

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 位整数)。

会有整数溢出,但是据我所知,是CPU的算术逻辑单元会溢出,所以编译器不参与这里。

它仍然是未定义的行为吗?

如果是,编译器如何检测溢出?

我能想到的最好的是 CPU 的溢出标志。如果是这种情况,是否意味着如果在执行时随时设置 CPU 的溢出标志,编译器可以为所欲为?

是的,但不一定是我认为你可能想要的方式,也就是说,如果在机器代码中有一个加法并且在运行时该加法换行(或以其他方式溢出,但在大多数体系结构上它会换行) 本身不是 UB。 UB 仅属于 C(或 C++)领域。该添加可能一直在添加无符号整数,或者是 编译器 可以进行的某种优化,因为它知道目标平台的语义并且可以安全地使用依赖于包装的优化(但是 不能,除非你使用无符号类型。

当然,这根本不意味着使用 "wrap only at runtime" 的结构是安全的,因为这些代码路径在编译时也是中毒的。例如在你的例子中,

extern void bar(void);

void foo(int x) {
    if (x + 150 < 5)
         bar();
}

由针对 x64 的 GCC 6.3 编译为

foo:
        cmp     edi, -145
        jl      .L4
        ret
.L4:
        jmp     bar

相当于

void foo(int x) {
    if (x < -145)
         bar(); // with tail call optimization
}

.. 如果您假设有符号整数溢出是不可能的(从某种意义上说,它在输入上放置了一个隐式前提条件,这样就不会发生溢出),这也是一样的。

你对第一个例子的分析不正确。 value += x; 等同于:

value = value + x;

在这种情况下 valueintxunsigned,所以 通常的算术转换 意味着 value 首先转换为无符号,因此我们有一个无符号加法,根据定义它不能溢出(它具有符合模运算的明确定义的语义)。

当未签名的结果被分配回 value 时,如果它大于 INT_MAX 那么这是一个超出范围的分配,它具有实现定义的行为。这不是溢出,因为它是赋值,而不是算术运算。

因此,哪些优化是可能的取决于实现如何定义整数超出范围赋值的行为。现代系统都采用具有相同 2 的补码表示的值,但历史上其他系统做了一些不同的事情。

所以原始示例在任何情况下都没有未定义的行为,建议的优化对于大多数系统来说是不可能的。


你的第二个例子与你的第一个例子无关,因为它不涉及任何无符号算术。如果 x > INT_MAX - 150 则表达式 x + 150 由于有符号整数溢出而导致未定义的行为。语言定义没有提到 ALU 或 CPU,所以我们可以确定这些东西与行为是否未定义无关。

If yes, how does the compiler detect the overflow?

不必。准确地说 因为 行为是未定义的,这意味着编译器不必担心溢出时会发生什么。它只需要发出一个可执行文件来举例说明所定义案例的行为。

在这个程序中,这些是 [INT_MININT_MAX-150] 范围内的输入,因此编译器可以将比较转换为 x < -145,因为这对所有对象都有相同的行为输入在明确定义的范围内,对于未定义的情况无关紧要。