在执行时上溢/下溢是否为未定义的行为?

时间:2017-01-09 23:49:45

标签: c++ c undefined-behavior integer-overflow

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

我很好理解这个例子(这是从Undefined Behavior page of Wikipedia中提取的):

  

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的溢出标志,编译器可以做任何他想做的事情?

2 个答案:

答案 0 :(得分:6)

是的,但不一定是我认为你可能意味着它的方式,也就是说,如果在机器代码中有一个添加,并且在运行时添加包装(或以其他方式溢出,但在大多数架构上它会包装)不是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
}

..如果您认为有符号整数溢出是不可能的(在某种意义上它将输入的隐式前提条件设置为不会发生溢出),这是相同的。

答案 1 :(得分:2)

您对第一个示例的分析不正确。 value += x;等效于:

value = value + x;

在这种情况下,valueint,而xunsigned,因此通常的算术转换意味着value是首先转换为无符号,所以我们有一个无符号加法,根据定义,它不会溢出(它按照模块化算法具有明确定义的语义)。

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

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

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


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

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

不是必须的。正是由于 行为未定义,这意味着编译器不必担心发生溢出时会发生什么情况。它仅需发出一个可执行文件,该可执行文件例示了所定义案例的行为。

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