为什么这个未使用的变量没有被优化掉?

时间:2017-11-02 09:53:55

标签: c++ gcc clang compiler-optimization

我和Godbolt的CompilerExplorer一起玩。我想看看某些优化是多么好。我的最低工作范例是:

#include <vector>

int foo() {
    std::vector<int> v {1, 2, 3, 4, 5};
    return v[4];
}

生成的汇编程序(通过clang 5.0.0,-O2 -std = c ++ 14):

foo(): # @foo()
  push rax
  mov edi, 20
  call operator new(unsigned long)
  mov rdi, rax
  call operator delete(void*)
  mov eax, 5
  pop rcx
  ret

正如人们所知,clang知道答案,但在返回之前会做很多事情。在我看来,即使是矢量也是由于“operator new / delete”而创建的。

任何人都可以向我解释这里发生了什么以及为什么它不仅仅会返回?

GCC生成的代码(此处未复制)似乎明确构造了向量。有谁知道海湾合作委员会无法推断出结果?

4 个答案:

答案 0 :(得分:29)

std::vector<T>是一个相当复杂的类,涉及动态分配。在clang++ is sometimes able to elide heap allocations期间,这是一个非常棘手的优化,您不应该依赖它。例如:

int foo() {
    int* p = new int{5};
    return *p;
}
foo():                                # @foo()
        mov     eax, 5
        ret

例如,使用std::array<T> (不动态分配) produces fully-inlined code

#include <array>

int foo() {
    std::array v{1, 2, 3, 4, 5};
    return v[4];
}
foo():                                # @foo()
        mov     eax, 5
        ret

正如Marc Glisse在其他答案的评论中所述,这就是标准在[expr.new] #10中所说的内容:

  

允许实现省略对可替换全局分配函数的调用([new.delete.single],[new.delete.array])。当它这样做时,存储由实现提供,或者通过扩展另一个新表达式的分配来提供。实现可以扩展新表达式e1的分配,以便为新表达式e2提供存储,如果以下情况属实,则分配未扩展:[...]

答案 1 :(得分:7)

如评论所述,operator new可以替换。这可能发生在任何翻译单位。因此,针对未被替换的案例优化程序需要整体程序分析。如果 被替换,你当然必须调用它。

默认operator new是否为库 I / O 调用未指定。这很重要,因为库I / O调用是可观察的,因此它们也不能被优化。

答案 2 :(得分:4)

N3664对[expr.new]的更改,在一个答案和一条评论中引用,允许 new-expression 不调用可替换的全局分配函数。但是vector使用std::allocator<T>::allocate分配内存,::operator new直接调用::operator new,而不是通过 new-expression 。因此,特殊许可不适用,通常编制者不能忽视对std::allocator<T>::allocate的直接调用。

然而,所有希望都没有丢失,因为​::​operator new的规范有this说:

  

备注:通过调用std::allocator获取存储空间,但未指定此函数调用的时间或频率。

利用此权限,libc ++的-stdlib=libc++ uses special clang built-ins向编译器指示允许使用elision。使用foo(): # @foo() mov eax, 5 ret clang compiles your code down to

import

答案 3 :(得分:0)

编译器无法优化与堆相关的代码,因为与堆相关的代码是特定于运行时的。与堆相关的代码是使用 std::vector,它将托管数据保存在堆内存中。

在您的示例中,所有值以及大小在编译时都是已知的,因此可以使用 std::array 代替,它由 aggregate initialization 初始化,因此可能是 {{3} }-合格。

使用 std::array 更改示例将函数减少到预期输出:

#include <array>

int foo() {
    std::array<int,5> v {1, 2, 3, 4, 5};
    return v[4];
}
foo():                                # @foo()
        mov     eax, 5
        ret

使用给定的函数仍然会导致调用 foo()。为了消除调用,函数必须被限定为constexpr

#include <array>

constexpr int foo() {
    constexpr std::array<int,5> v {1, 2, 3, 4, 5};
    return v[4];
}

int main() {
    return foo();
}
main:                                   # @main
        mov     eax, 5
        ret