我想遍历从signed int
到INT_MIN
的INT_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
报告为clang。 check
被编译为简单的return 0
。但是,将单行(第22行从break;
更改为x = INT_MIN;
)changes completely the behavior:check
变为无循环无限循环,但是main
现在显示counterexample found
。即使使用-std=c11
,所有这一切都发生。
我担心的是,我不能信任第一个版本,因为在第二种情况下,编译器可能未完成正确的操作。
问题是是否允许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的性能几乎没有区别(但大小没有区别)。