让我们搞乱非常基本的动态分配内存.我们取3的向量,设置其元素并返回向量的总和.
在第一个测试用例中,我使用带new[]
/ 的原始指针delete[]
.在我使用的第二个std::vector
:
#includeint main() { //int *v = new int[3]; // (1) auto v = std::vector (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>::~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
:调用new
,设置内存然后调用delete
.
我意识到这很可能是一个几乎不可能给出的答案,但也许有人有一些见解,可能会出现一些有趣的答案.
std::vector
在原始内存分配情况下,哪些因素不允许编译器优化删除内存分配?
当使用指向动态分配的数组的指针(直接使用new []和delete [])时,编译器优化了对它们的调用operator new
,operator delete
即使它们具有可观察到的副作用.C++标准第5.3.4节第10段允许这种优化:
允许实现省略对可替换全局分配函数的调用(18.6.1.1,18.6.1.2).当它这样做时,存储由实现提供或者......
我会在结尾处显示句子的其余部分,这是至关重要的.
这种优化相对较新,因为它首先在C++ 14(提案N3664)中被允许.Clang从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
除了mov qword ptr [rax], 0
来自std :: vector的构造函数之外(编译器应该删除它但由于其优化算法存在缺陷而无法执行此操作).