C:如何迭代 `signed int` 的所有可能值,从 `INT_MIN` 到 `INT_MAX` ?

标签 c integer infinite-loop signed integer-overflow

我想遍历 signed int 的所有可能值,来自 INT_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)版本。我的问题如下。

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

我知道它不能用于 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 counterexampleclang . check被编译成一个简单的 return 0 .但是,更改单行(第 22 行从 break;x = INT_MIN; )changes completely the behavior :check变成 noop 无限循环,但 main现在打印 counterexample found .即使使用 -std=c11,这一切也会发生.

我担心的是我不能相信第一个版本,因为编译器可能没有做正确的事情,就像在第二种情况下一样。

最佳答案

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

  1. An iteration statement whose controlling expression is not a constant expression,156) that performs no input/output operations, does not access volatile objects, and performs no synchronization or atomic operations in its body, controlling expression, or (in the case of a for statement) its expression-3, may be assumed by the implementation to terminate.157)


IE。允许编译器优化没有任何副作用并且确实具有非常量控制表达式的循环。 IE。当涉及到 as-if 规则时,这是一个完全无用的循环。此异常(exception)已在标准的 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 几乎没有区别(但在大小上没有区别)。

关于C:如何迭代 `signed int` 的所有可能值,从 `INT_MIN` 到 `INT_MAX` ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59327636/

相关文章:

C++ - isdigit 无法正常工作并导致永无止境的循环

c - 在 Windows 中找不到 libxml/xmlversion.h 文件

c++ - 我如何判断 OSX 程序是否正在调试器中运行?

sql - 如何找到整数序列中的第一个中断点?

sql-server-2005 - 如何在 T-SQL 中将 DateTime 转换为精度大于天的数字?

ios - 自动布局导致无限 layoutSubviews 循环

debugging - 为什么prolog进入无限循环?

c - 泛型与类型安全?在 C 中使用 void*

c - 堆栈还是堆?对于已知大小的变量,选择哪一个?

Java枚举、整数和字符串一起定义?