首页 > 解决方案 > 为什么启用未定义的行为清理会干扰优化?

问题描述

考虑以下代码:

#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 危害......

笔记:

标签: c++gcccompiler-optimizationconstexprubsan

解决方案


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

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

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

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

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

推荐阅读