让我们搞乱非常基本的动态分配内存。我们取3的向量,设置其元素并返回向量的总和。
在第一个测试用例中,我使用了一个带有new[]
/ delete[]
的原始指针。在第二部分中,我使用了std::vector
:
#include <vector>
int main()
{
//int *v = new int[3]; // (1)
auto v = std::vector<int>(3); // (2)
for (int i = 0; i < 3; ++i)
v[i] = i + 1;
int s = 0;
for (int i = 0; i < 3; ++i)
s += v[i];
//delete[] v; // (1)
return s;
}
大会(1)(new[]
/ delete[]
)
main: # @main
mov eax, 6
ret
(2)(std::vector
)
main: # @main
push rax
mov edi, 12
call operator new(unsigned long)
mov qword ptr [rax], 0
movabs rcx, 8589934593
mov qword ptr [rax], rcx
mov dword ptr [rax + 8], 3
test rax, rax
je .LBB0_2
mov rdi, rax
call operator delete(void*)
.LBB0_2: # %std::vector<int, std::allocator<int> >::~vector() [clone .exit]
mov eax, 6
pop rdx
ret
两个输出均来自https://gcc.godbolt.org/ -std=c++14 -O3
在两个版本中,返回值都是在编译时计算的,因此我们只看到mov eax, 6; ret
。
使用原始new[]
/ delete[]
,动态分配已完全删除。但是,使用std::vector
时,将分配,设置和释放内存。
即使使用未使用的变量,也会发生 auto v = std::vector<int>(3)
:调用new
,设置内存然后调用delete
。
我意识到这很可能是一个几乎不可能给出的答案,但也许某人有一些见解,可能会出现一些有趣的答案。
在std::vector
情况下,允许编译器优化删除内存分配的原因是什么,如原始内存分配情况?
答案 0 :(得分:14)
当使用指向动态分配的数组的指针(直接使用new []和delete [])时,编译器优化了对operator new
和operator delete
的调用,即使它们具有可观察到的副作用。 C ++标准第5.3.4节第10段允许进行此优化:
允许实现省略对可替换全局的调用 分配函数(18.6.1.1,18.6.1.2)。当它这样做时,存储 而是由实现提供或...
我会在最后显示句子的其余部分,这是至关重要的。
此优化相对较新,因为它首先在C ++ 14中被允许(提案N3664)。 Clang supported it since 3.4。最新版本的gcc,即5.3.0,并没有利用as-if规则的这种放宽。它产生以下代码:
main:
sub rsp, 8
mov edi, 12
call operator new[](unsigned long)
mov DWORD PTR [rax], 1
mov DWORD PTR [rax+4], 2
mov rdi, rax
mov DWORD PTR [rax+8], 3
call operator delete[](void*)
mov eax, 6
add rsp, 8
ret
MSVC 2013也不支持此优化。它产生以下代码:
main:
sub rsp,28h
mov ecx,0Ch
call operator new[] ()
mov rcx,rax
mov dword ptr [rax],1
mov dword ptr [rax+4],2
mov dword ptr [rax+8],3
call operator delete[] ()
mov eax,6
add rsp,28h
ret
我目前无法访问MSVC 2015 Update 1,因此我不知道它是否支持此优化。
最后,这是icc 13.0.1生成的汇编代码:
main:
push rbp
mov rbp, rsp
and rsp, -128
sub rsp, 128
mov edi, 3
call __intel_new_proc_init
stmxcsr DWORD PTR [rsp]
mov edi, 12
or DWORD PTR [rsp], 32832
ldmxcsr DWORD PTR [rsp]
call operator new[](unsigned long)
mov rdi, rax
mov DWORD PTR [rax], 1
mov DWORD PTR [4+rax], 2
mov DWORD PTR [8+rax], 3
call operator delete[](void*)
mov eax, 6
mov rsp, rbp
pop rbp
ret
显然,它不支持这种优化。我无法访问最新版本的icc,即16.0。
所有这些代码段都是在启用优化的情况下生成的。
使用std::vector
时,所有这些编译器都没有优化分配。当编译器没有执行优化时,它可能是因为它由于某种原因而不能或者它还没有得到支持。
不允许编译的因素是什么? 优化以删除std :: vector中的内存分配, 比如在原始内存分配情况下?
编译器没有执行优化,因为它不被允许。为了看到这一点,让我们看看5.3.4中第10段句子的其余部分:
允许实现省略对可替换全局的调用 分配函数(18.6.1.1,18.6.1.2)。当它这样做时,存储 而是由实现提供或通过扩展提供 分配另一个新表达。
这就是说,只有当它来自new-expression时,才能省略对可替换全局分配函数的调用。新表达式在同一部分的第1段中定义。
以下表达式
new int[3]
是一个new-expression,因此允许编译器优化掉相关的分配函数调用。
另一方面,以下表达式:
::operator new(12)
不是新表达式(见5.3.4第1段)。这只是一个函数调用表达式。换句话说,这被视为典型的函数调用。此函数无法优化,因为它从另一个共享库导入(即使您静态链接运行时,函数本身也调用另一个导入的函数)。
std::vector
使用的默认分配器使用::operator new
分配内存,因此不允许编译器对其进行优化。
让我们测试一下。这是代码:
int main()
{
int *v = (int*)::operator new(12);
for (int i = 0; i < 3; ++i)
v[i] = i + 1;
int s = 0;
for (int i = 0; i < 3; ++i)
s += v[i];
delete v;
return s;
}
通过使用Clang 3.7进行编译,我们得到以下汇编代码:
main: # @main
push rax
mov edi, 12
call operator new(unsigned long)
movabs rcx, 8589934593
mov qword ptr [rax], rcx
mov dword ptr [rax + 8], 3
test rax, rax
je .LBB0_2
mov rdi, rax
call operator delete(void*)
.LBB0_2:
mov eax, 6
pop rdx
ret
这与使用std::vector
时生成的汇编代码完全相同,除了来自std :: vector的构造函数的mov qword ptr [rax], 0
之外(编译器应该删除它但是由于优化算法的一个缺陷)。