为什么我真的只需要一个,却创建了两个对象

Why are there two objects created when I only really need one?

本文关键字:创建 对象 两个 一个 真的 为什么      更新时间:2023-10-16

我刚刚编译了这个简单的片段:

#include <iostream>
#include <string>
std::string foo()
{
    return std::string("bar");
}
int main()
{
    std::string test = foo();
    std::cout << test << std::endl;
    return 0;
}

使用-O2优化,却发现正在创建两个std::string对象。当我转储二进制文件时,objdump显示~basic_string被调用了两次。

0000000000400900 <main>:
  400900:   53                      push   %rbx
  400901:   48 83 ec 10             sub    $0x10,%rsp
  400905:   48 89 e7                mov    %rsp,%rdi
  400908:   e8 73 01 00 00          callq  400a80 <foo()>
  40090d:   48 89 e6                mov    %rsp,%rsi
  400910:   bf 80 10 60 00          mov    $0x601080,%edi
  400915:   e8 a6 ff ff ff          callq  4008c0 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <char, std::char_traits<char>, std::allocator<char> >(std::basic_ostream<char, std::char_traits<char> >&, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)@plt>
  40091a:   48 89 c7                mov    %rax,%rdi
  40091d:   e8 ae ff ff ff          callq  4008d0 <std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)@plt>
  400922:   48 89 e7                mov    %rsp,%rdi
  400925:   e8 76 ff ff ff          callq  4008a0 <std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()@plt>
  40092a:   48 83 c4 10             add    $0x10,%rsp
  40092e:   31 c0                   xor    %eax,%eax
  400930:   5b                      pop    %rbx
  400931:   c3                      retq   
  400932:   48 89 c3                mov    %rax,%rbx
  400935:   48 89 e7                mov    %rsp,%rdi
  400938:   e8 63 ff ff ff          callq  4008a0 <std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()@plt>
  40093d:   48 89 df                mov    %rbx,%rdi
  400940:   e8 ab ff ff ff          callq  4008f0 <_Unwind_Resume@plt>
  400945:   66 66 2e 0f 1f 84 00    data32 nopw %cs:0x0(%rax,%rax,1)
  40094c:   00 00 00 00 

由于我实际上只需要一个对象,所以我考虑使用右值引用来捕获返回的值foo()。所以我把代码行改为std::string && test = foo();。奇怪的是,objdump仍然显示两个被调用的析构函数。有人能解释一下为什么吗?

第一个析构函数调用后面跟着几个指令,然后是retq:

  400925:   e8 76 ff ff ff          callq  4008a0 <std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()@plt>
  ...
  400931:   c3                      retq   

这是正常的代码流。

400932的后续mov开始,是内部用于展开具有异常传播的堆栈的代码,通常称为着陆台

  400932:   48 89 c3                mov    %rax,%rbx
  400935:   48 89 e7                mov    %rsp,%rdi
  400938:   e8 63 ff ff ff          callq  4008a0 <std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()@plt>
  40093d:   48 89 df                mov    %rbx,%rdi
  400940:   e8 ab ff ff ff          callq  4008f0 <_Unwind_Resume@plt>

以下是GCC要说的:

  • 生成异常处理着陆台

    此过程生成用于处理异常处理库例程和函数内异常处理程序之间通信的粘合。异常处理库调用的函数中的入口点称为着陆台。此通行证的代码位于except.c中。

正如您所看到的,控制流的路径是完全不同的,因此无论哪种方式,析构函数都只能调用一次。

_Unwind_Resume是AMD64和安腾C++ABI的一部分,作为展开调用堆栈直到到达能够捕获异常类型的函数的一种方法。你需要做一些挖掘,才能从谷歌上找到更多关于它的信息。这里有一个很好的资源来讨论它。

_展开资源

void _Unwind_Resume
(struct _Unwind_Exception *exception_object);

恢复现有异常的传播,例如在部分展开的堆栈中执行清理代码之后。对该例程的调用插入到执行清理但未恢复正常执行的着陆台的末尾。这会导致退市进一步进行。

_Unwind_Resume不应用于实现重新思考。对于展开运行时,重新引发的catch代码是一个处理程序,并且在进入前一个展开会话已终止。通过使用相同的异常对象再次调用_Unwind_RaiseException来实现重新引发。

这是展开库中唯一一个预期由生成的代码直接调用的例程:它将在"着陆台"模型中的着陆台末端调用。