我和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生成的代码(此处未复制)似乎明确构造了向量。有谁知道海湾合作委员会无法推断出结果?
答案 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