16

考虑以下代码:

#include <string_view>

constexpr std::string_view f() { return "hello"; }

static constexpr std::string_view g() {
    auto x = f();
    return x.substr(1, 3);
}

int foo() { return g().length(); }

如果我用 GCC 10.2 和 flags 编译它--std=c++17 -O1,我会得到:

foo():
        mov     eax, 3
        ret

此外,据我所知,此代码不会遇到任何未定义的行为问题。

但是 - 如果我添加 flag -fsanitize=undefined,编译结果是:

.LC0:
        .string "hello"
foo():
        sub     rsp, 104
        mov     QWORD PTR [rsp+80], 5
        mov     QWORD PTR [rsp+16], 5
        mov     QWORD PTR [rsp+24], OFFSET FLAT:.LC0
        mov     QWORD PTR [rsp+8], 3
        mov     QWORD PTR [rsp+72], 4
        mov     eax, OFFSET FLAT:.LC0
        cmp     rax, -1
        jnb     .L4
.L2:
        mov     eax, 3
        add     rsp, 104
        ret
.L4:
        mov     edx, OFFSET FLAT:.LC0+1
        mov     rsi, rax
        mov     edi, OFFSET FLAT:.Lubsan_data154
        call    __ubsan_handle_pointer_overflow
        jmp     .L2
.LC1:
        .string "/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/string_view"
.Lubsan_data154:
        .quad   .LC1
        .long   287
        .long   49

看到这个Compiler Explorer

我的问题:为什么清理会干扰优化?特别是因为代码似乎没有任何 UB 危害......

笔记:

  • 我怀疑是 GCC 错误,但也许我对 UBsan 所做的事情有错误的认识。
  • 如果我设置相同的行为-O3
  • 在没有优化标志的情况下,无论是否经过清理,都会生成较长的代码。
  • 如果您声明xconstexpr变量,则清理不会阻止优化。
  • 与 C++17 和 C++20 的行为相同。
  • 使用 Clang,您也会得到这种差异,但只能使用更高的优化设置(例如-O3)。
4

3 回答 3

10

Sanitizers 添加了必要的工具来在运行时检测违规行为。该检测可能会通过引入一些不透明的调用/副作用来阻止在编译时计算函数作为优化,否则这些调用/副作用不会出现。

您看到的不一致行为是因为g().length();调用不是在constexpr上下文中完成的,因此不需要在编译时计算(嗯,“不期望”会更准确)。GCC 可能有一些启发式方法来计算在常规上下文中constexpr带有参数的函数,一旦消毒剂通过破坏函数的 -ness(由于添加的仪器)或所涉及的启发式方法之一而参与其中,这些constexpr参数就不会触发。constexpr

添加constexprxmake f()call 一个常量表达式(即使g()不是),所以它是在编译时编译的,所以它不需要被检测,这足以触发其他优化。

人们可以将其视为 QoI 问题,但总的来说,它是有道理的

  1. constexpr函数评估可能需要任意长的时间,因此在编译时评估所有内容并不总是可取的,除非被要求
  2. 您始终可以通过在常量表达式中使用此类函数来“强制”此类评估(尽管在这种情况下该标准有些许可)。这也会为您处理任何 UB。
于 2020-10-23T16:15:16.117 回答
5

特别是因为代码似乎没有任何 UB 危害

f()返回std::string_view包含长度和指针的 a。调用x.substr(1, 3)需要向该指针添加一个。从技术上讲,这可能会溢出。那是潜在的UB。将 1 更改为 0 并看到 UB 代码消失。

We know that [ptr, ptr+5] are valid, so the conclusion is that gcc fails to propagate that knowledge of the value range, despite aggressive inlining and other simplification.

I can't find a directly related gcc bug, but this comment seems interesting:

[VRP] does an incredibly bad job at tracking pointer ranges where it simply prefers to track non-NULL.

于 2020-10-23T20:07:06.127 回答
3

未定义的行为清理器不是仅编译器时的机制(重点不在原版中;引用是关于 clang,但它也适用于 GCC):

UndefinedBehaviorSanitizer (UBSan) 是一种快速的未定义行为检测器。UBSan 在编译时修改程序以在程序执行期间捕获各种未定义的行为。

因此,代替原始程序 - 实际编译的是带有一些额外“工具”的程序,您可以在较长的编译代码中看到这些程序,例如:

  • 原始程序无法获得的附加指令。
  • 指示在标准库代码中与不适当执行的​​代码相关的位置。

显然,GCC 的优化器无法检测到实际上不会有任何未定义的行为,并丢弃未使用的代码。

于 2020-10-23T16:15:47.217 回答