【问题标题】:gcc -O2 creates an endless loop, probably because of undefined behaviourgcc -O2 创建一个无限循环,可能是因为未定义的行为
【发布时间】: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(;;); 等效项。

标签: c gcc


【解决方案1】:

我看不懂代码也没有缩进,但是循环很奇怪。无论如何:

“-Wall -Wextra -pedantic”甚至不发出警告之类的。

还有其他检测UB的方法!使用更多 -fsanitize=* 选项编译您的代码,结果如下:

+ gcc -Wall -Wextra -ggdb3 -fsanitize=address -fsanitize=undefined -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize-address-use-after-scope /tmp/1.c
/tmp/1.c:22:29: runtime error: index 9 out of bounds for type 'unsigned int [9]'
/tmp/1.c:22:29: runtime error: load of address 0x7ffc48c7a894 with insufficient space for an object of type 'unsigned int'
0x7ffc48c7a894: note: pointer points here
  13 00 00 00 60 60 00 00  00 00 00 00 00 00 00 00  01 00 00 00 00 00 00 00  08 aa c7 48 fc 7f 00 00
              ^ 
=================================================================
==73835==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffc48c7a894 at pc 0x55c28a5bf773 bp 0x7ffc48c7a800 sp 0x7ffc48c7a7f0
READ of size 4 at 0x7ffc48c7a894 thread T0
    #0 0x55c28a5bf772 in main /tmp/1.c:22
    #1 0x7f0060bd1151 in __libc_start_main (/usr/lib/libc.so.6+0x28151)
    #2 0x55c28a5bf12d in _start (/tmp/tmp.qv8ZsZofOJ.out+0x112d)

Address 0x7ffc48c7a894 is located in stack of thread T0 at offset 84 in frame
    #0 0x55c28a5bf208 in main /tmp/1.c:3

  This frame has 1 object(s):
    [48, 84) 'busses' (line 4) <== Memory access at offset 84 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow /tmp/1.c:22 in main
Shadow bytes around the buggy address:
  0x1000091874c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000091874d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000091874e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000091874f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100009187500: 00 00 00 00 00 00 00 00 f1 f1 f1 f1 f1 f1 00 00
=>0x100009187510: 00 00[04]f3 f3 f3 f3 f3 00 00 00 00 00 00 00 00
  0x100009187520: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100009187530: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100009187540: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100009187550: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100009187560: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  Shadow gap:              cc
==73835==ABORTING

快速检查后,对busses 的越界访问发生在此处:

offset += busses[++i];

有人知道如何修补这种未定义的行为吗?

不知道,但是写一个不会越界访问数组的算法。

【讨论】:

    猜你喜欢
    • 2015-04-22
    • 2021-05-14
    • 2013-03-13
    • 2015-01-24
    • 2018-02-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多