请考虑以下代码:
#include <utility>
#include <string>
int bar() {
std::pair<int, std::string> p {
123, "Hey... no small-string optimization for me please!" };
return p.first;
}
我希望该功能可以简单实现:
bar():
mov eax, 123
ret
但是,实现调用了operator new()
,使用我的文字构造了一个std::string
,然后调用了operator delete()
。至少-gcc 9和clang 9会这样做(GodBolt)。这是clang输出:
bar(): # @bar()
push rbx
sub rsp, 48
mov dword ptr [rsp + 8], 123
lea rax, [rsp + 32]
mov qword ptr [rsp + 16], rax
mov edi, 51
call operator new(unsigned long)
mov qword ptr [rsp + 16], rax
mov qword ptr [rsp + 32], 50
movups xmm0, xmmword ptr [rip + .L.str]
movups xmmword ptr [rax], xmm0
movups xmm0, xmmword ptr [rip + .L.str+16]
movups xmmword ptr [rax + 16], xmm0
movups xmm0, xmmword ptr [rip + .L.str+32]
movups xmmword ptr [rax + 32], xmm0
mov word ptr [rax + 48], 8549
mov qword ptr [rsp + 24], 50
mov byte ptr [rax + 50], 0
mov ebx, dword ptr [rsp + 8]
mov rdi, rax
call operator delete(void*)
mov eax, ebx
add rsp, 48
pop rbx
ret
.L.str:
.asciz "Hey... no small-string optimization for me please!"
我的问题是:显然,编译器将所有内容放在“它的前面”,以查看发生了什么。为什么不将字符串消除掉?更具体地说:
new()
和delete()
之间的代码,编译器知道该AFAICT不会产生有用的结果。new()
和delete()
会自称。毕竟,标准AFAIK允许进行小字符串优化,因此即使clang / gcc尚未选择使用它,它也[[could可以使用;表示实际上并不需要在此处调用new()
或delete()
。编译器必须删除该字符串的唯一依据是基于“好像”规则。也就是说,字符串创建/销毁的行为对用户是否可见,因此无法删除?
由于使用std::allocator
和标准字符特征,因此basic_string
的构造和销毁本身不会被用户覆盖。因此,存在这样的想法,即字符串的创建不是函数调用的可见副作用,因此可以根据“好像”规则将其删除。
但是,由于指定了std::allocator::allocate
来调用::operator new
,并且operator new
是全局可替换的,因此有理由认为这是构造这种字符串的明显副作用。因此,编译器无法按照“好像”规则将其删除。
如果编译器知道您尚未替换operator new
,则理论上它可以将字符串优化掉。
这并不意味着任何特定的编译器
将都会这样做。