3

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

#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();改成 Weirdly,objdump 仍然显示调用了两个析构函数。谁能解释我为什么?

4

1 回答 1

9

第一个析构函数调用之后是几条指令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   

这是正常的代码流程。

从后面mov的 at开始400932是内部用于通过异常传播展开堆栈的代码,通常称为landing pad

  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 和 Itanium C++ ABI 的一部分,用于展开调用堆栈,直到它到达能够捕获异常类型的函数。您需要进行一些挖掘才能从 Google 中找到有关它的大量信息。是一个很好的资源来讨论它。

_ Unwind_Resume

void _Unwind_Resume
(struct _Unwind_Exception *exception_object);

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

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

这是 unwind 库中唯一预计由生成的代码直接调用的例程:它将在“着陆垫”模型中的着陆垫结束时调用。

于 2012-08-29T01:38:57.997 回答