C:如何迭代所有有符号的int值,从INT_MIN到INT_MAX?

问题描述 投票:2回答:1

我想遍历从signed intINT_MININT_MAX的所有可能值。明显的代码

for (int x = INT_MIN; x <= INT_MAX; x++)
    do_something(x);

由于有符号整数溢出引起的不确定行为而被破坏。值得注意的是,它显然是聪明的变体。

int x = INT_MIN;
do {
    do_something(x);
} while (x++ < INT_MAX);

[有趣的是,在第二个示例中,clang -O2 -fsanitize=undefined正确报告了runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int',而gcc -O2 -fsanitize=undefined没有报告。我想象gcc-fsanitize=signed-integer-overflow-ftrapv一样坏。

我可以找到的表达这种迭代的唯一方法是使用goto

int x = INT_MIN;
loop: {
    do_something(x);
    if (x < INT_MAX) {
        x++;
        goto loop;
    }
}

使用无限循环并手动break

int x = INT_MIN;
while (1) {
    do_something(x);
    if (x < INT_MAX)
        x++;
    else
        break;
}

并且仅在安全的情况下利用短路评估来增加变量:

int x = INT_MIN;
do {
    do_something(x);
} while (x < INT_MAX && ++x != INT_MIN);

Steve Jessop指出,在do-while解决方案中,可以改用while (x < INT_MAX && (++x, 1));

在这三个版本中,我不是特别反对goto版本,我不太喜欢do-while版本(尤其是++x != INT_MIN位...),但是总的来说,我更喜欢while (1)版本。我的问题是以下。

如果while (1)不执行输入/输出操作,也不访问易失性对象,是否允许C编译器完全消除无限do_something循环?

我知道它不能用于C11,但我不太确定以前的版本。

编辑

我将尝试更详细地描述我担心的事情。假设我正在使用此循环来验证其他一些代码的正确性。例如,我可能会这样做

#include <stdio.h>
#include <limits.h>

int add_wrap_unsigned(int x, int y) {
    return (unsigned) x + (unsigned) y;
}

int add_wrap_builtin(int x, int y) {
    int res;
    __builtin_add_overflow(x, y, &res);
    return res;
}

int check() {
    int x = INT_MIN;
    while (1) {
        if (add_wrap_unsigned(x, 1) != add_wrap_builtin(x, 1))
            return 1;
        if (x < INT_MAX)
            x++;
        else
            break;
    }
    return 0;
}

int main() {
    if (check())
        printf("counterexample found");
    else
        printf("no counterexample");
    return 0;
}

此正确将no counterexample报告为clangcheck被编译为简单的return 0。但是,将单行(第22行从break;更改为x = INT_MIN;changes completely the behaviorcheck变为无循环无限循环,但是main现在显示counterexample found。即使使用-std=c11,所有这一切都发生。

我担心的是,我不能信任第一个版本,因为在第二种情况下,编译器可能未完成正确的操作。

c integer infinite-loop signed integer-overflow
1个回答
2
投票

问题是是否允许C编译器删除无限循环。不,不允许删除其控制表达式为常数的无限循环(for (;;) {}while (1) { }),但是C标准[C11/C17 6.8.5p6中的[[明确说明]

    [其控制表达式不是常数表达式的迭代语句,156)不执行输入/输出操作,不访问易失性对象,并且在其主体,控制表达式中(或在以下情况下)不执行同步或原子操作: for语句)其表达式3,可以被实现假定为终止。157)
  • 即允许编译器优化一个没有

    任何副作用且确实具有非恒定控制表达式的循环。即就as-if规则而言,它是完全没用的循环。在标准的C11修订版中已授予此例外。这在C11之前不适用,因此,如果存在该行为,则表明它不符合标准。您不需要保护增量,只需break


    for (int x = INT_MIN; ; x++) { do_something(x); if (x == INT_MAX) break; }

    在这种情况下,控制表达式是一个常数表达式(隐式真值),因此

    必须根据C11 / C17 6.8.5p6进行优化,但编译器必须明确证明它可以终止。] >

    现在优化的内容取决于编译器和标志-使用-O3 Clang 9.0会将其编译为等效的


    for (int x = INT_MIN; x != INT_MIN; x++) do_something(x)

    如果溢出是用环绕式定义的-但是我们知道,x86机器代码确实具有定义良好的环绕式。

  • [GCC 9.2产生的机器代码的行为类似于

    int x = INT_MIN; do_something(x); do { do_something(++x); } while (x < INT_MAX);

    用C语言编写的代码将

    没有未定义的行为
    ,并且其性能与Clang的性能几乎没有区别(但大小没有区别)。
    © www.soinside.com 2019 - 2024. All rights reserved.