求助:GCC 开启 O2 后程序行为不符合预期

学术版

@[NGC5457](/user/241614) 我认为是memset -> movaps的操作可能影响了结果 你可以看一下movaps的操作数是什么,这条指令就是把一块内存mov了一下 有可能是编译器为了优化把一块全0的内存mov过去了(我猜的)
by LiuTianyou @ 2023-01-02 16:58:37


或许是这个问题 https://moransky.blog.uoj.ac/blog/7735
by reveal @ 2023-01-02 16:59:46


你编译个最新gcc试试
by LiuTianyou @ 2023-01-02 17:03:08


> 出现这种现象的根本原因是什么? 你的猜想是对的,数组越界是 UB。源于长为 n 的数组的指针,其下标 i 必须满足 0 ≤ i ≤ n,且仅在 i ≠ n 时可用 `*` 解引用。
by ud2_ @ 2023-01-02 17:07:54


@[ud2_](/user/206953) 但我并没有在过程里对这个非法地址解引用。我还猜想由于 `-O2` 会尝试计算表达式的常数值,`gcc` 自己把那个非法地址解引用然后用在后面的优化里了。我猜您是这个意思。 所以规避方法还是避免悬置指针的使用是吧?
by NGC5457 @ 2023-01-02 17:16:24


@[reveal](/user/523491) 谢谢,不过里面有个链接指向的 issue 提到这个问题只在 `GCC 10.x, 11.x, 12.x` 的 build 里面修复了。CCF 用的 `GCC 9.3.0` 没辙。
by NGC5457 @ 2023-01-02 17:19:02


@[LiuTianyou](/user/206814) 我不是很会看汇编,但是下面这一段的意思大概就是把 `xmm0` 的地址自己把自己异或了(全0)然后复制到 `mx[0], mx[1]` 里头去。 ```cpp pxor xmm0, xmm0 lea rdi, [r12+16] lea rsi, [rbp+16] mov eax, OFFSET FLAT:seg_tree::merge(seg_tree::info const&, seg_tree::info const&) const::mx-8 movaps XMMWORD PTR seg_tree::merge(seg_tree::info const&, seg_tree::info const&) const::mx[rip], xmm0 movaps XMMWORD PTR seg_tree::merge(seg_tree::info const&, seg_tree::info const&) const::mx[rip+16], xmm0 ```
by NGC5457 @ 2023-01-02 17:23:55


@[NGC5457](/user/241614) 是这样的。 [\[expr.add\] 对指针加减的说明](https://eel.is/c++draft/expr.add#4)。$\tiny\textsf{标准本身没有悬置指针的概念因为没法不通过 UB 产生这样的指针。}$
by ud2_ @ 2023-01-02 17:24:03


我仔细阅读了第一份代码开 `-O2` 的汇编,发现 `return` 语句对应的命令中,*默认 `mx[0].fst == 0` 为真* 然后直接把 `0` 赋值给了返回值结构体的第一个长为 $4$ 的字(`DWORD`),而其他部分则是老老实实复制的源地址的值;第二份代码以及后来的改进方案中,均没有该问题。 还注意到,在第二份代码的 `-O2` 汇编中,`fprintf` 的第三个参数(`mx[0].fst`)也直接赋值 `0`,但神奇的是它的 `return` 函数是正确的。 可以推测,没有 `memset` 的情况下,它并不敢作出上述假设;而 `pt` 不是悬置指针的情况下,不会出现上述问题。因此我们是否可以认为,是 `pt` 的未定义行为、`memset` 全零赋值和 `gcc` 的优化 bug 的共同作用导致的本帖的问题。
by NGC5457 @ 2023-01-02 18:19:53


|