【发布时间】:2020-12-16 13:51:56
【问题描述】:
我编写此 C 代码是为了解决 Advent of Code 13 2020。我知道,尝试通过蛮力解决它可能不可行,但程序为示例输入提供了正确答案。
如果我尝试让 gcc 优化代码,它会使用 -O1 给出正确的结果,但使用 -O2 会创建一个无限循环。经过所有研究,我的结论是我的代码中存在未定义的行为,我想这与“找到”可能永远不会高于 0 的概率有关,因此“时间”会溢出。
问题来了:有人知道如何修补这种未定义的行为吗?
“-Wall -Wextra -pedantic”甚至不发出警告之类的。
我只是找不到解决方案。例如,如果我将 while 循环的头部更改为 (!found && time
这是代码,正确的结果是“1068781”:
#include <stdio.h>
int main()
{
unsigned int busses[] = {7, 0, 13, 2, 59, 1, 31, 0, 19};
unsigned int busses_used = 9;
unsigned int i = 0;
unsigned int found = 0;
unsigned long long time = 0;
unsigned int offset = 0;
unsigned int increment = 7;
while (!found) {
time += increment;
offset = 0;
for (i = 0; i < busses_used; i++) {
if ((time + offset) % busses[i] == 0) {
found = 1;
offset++;
} else {
found = 0;
break;
}
offset += busses[++i];
}
}
printf("Endtime: %lld\n", time);
return 0;
}
编辑:感谢 KamilCuk 指出代码正在越界访问数组并教导如何找出它正在这样做。该问题通过在“busses”数组的末尾添加另一个 0 来解决,因此也将“busses_used”设置为 10 而不是 9。
【问题讨论】:
-
(time + offset) % busses[i]创建一个未定义的行为,因为busses包含零,并且除以零的除法/获取提醒是一个UB。 e.g. this SO answer -
有趣的是,
% busses[i]永远不会为零,因为offset += busses[++i]-i每个循环递增两次。 -
即使你修复了 UB,由于循环行为不依赖于任何输入并且不输出任何内容,因此允许优化器准确确定
printf之前的所有代码会提前做,基本上只是用unsigned long long time = THE_ANSWER;语句替换它。 - 或者,如果 t 可以证明循环永远不会结束,则为for(;;);等效项。