4

https://stackoverflow.com/a/6614369/1091587简要介绍了当您阅读使用“gcc3”类型名称修饰编译的程序的符号表时出现的析构函数类型(D0、D1、D2)。还有对应的构造函数C0/C1/C2。对于 g++-4.7(可能更早),出现了一个新的 ctor/dtor 对,即 C5/D5,但仅作为调试符号。

$ cat i.cpp 
class X { public: virtual ~X() {}; };
int main(void) { X x; return 0; };
$ g++ -c i.cpp 
$ nm i.o | grep 5
0000000000000000 n _ZN1XC5Ev
0000000000000000 n _ZN1XD5Ev
$ c++filt -n _ZN1XC5Ev _ZN1XD5Ev
X::X()
X::~X()

demangler 源将 D5 对象称为“gnu_v3_object_dtor_group”,但 dtor 组到底是什么,它有什么用处?clang++-3.3 不会发出它,并且http://gcc.gnu.org/ml/gcc-patches/2011-11/msg00383.html表明它可能与 gcc 中的新事务内存功能有关。

4

1 回答 1

4

这个 LLVM 补丁这个 GCC 错误提供了更多背景信息。通过以下链接,我发现错误 3187 - gcc 放置了两个构造函数副本,这似乎是这一切的起源:

构造函数和析构函数的两个(有时三个)相同的副本被放置。链接器不会失败,但生成的二进制文件(在我们的实际示例中)比必要的要大 20%。

如果您搜索“PR c++/3187”(例如),您可以找到许多关于 gcc-patches ML 的讨论。基本上,C5/D5 本身不是构造函数/析构函数,而是包含两个或多个“基本”构造函数/析构函数的COMDAT 组。这确保了组中的函数要么全部用于最终二进制文件,要么全部丢弃(以强制执行“一个定义规则”)。

上述bug中讨论的结果似乎是:

对于任何类,实现都可以选择每个构造函数/析构函数使用一个 comdat 或使用 C5/D5 comdat。我可以根据任何盈利标准做出决定。如果使用 C5/D5 comdat,则规则为

  • C5 comdat 必须具有 C1 和 C2。
  • 如果类有虚析构函数,则 D5 comdat 必须有 D0、D1 和 D2
  • 如果一个类具有非虚拟析构函数,则 D5 comdat 必须只有 D1 和 D2 析构函数。即使实现使用 D0 而不是调用 D1 + _ZdlPv 来实现“delete *x”也是如此

您可以通过以下方式转储文件来查看 comdats readelf -G

COMDAT group section [    1] `.group' [_ZN1XD5Ev] contains 2 sections:
   [Index]    Name
   [   10]   .text._ZN1XD2Ev
   [   12]   .text._ZN1XD0Ev

COMDAT group section [    2] `.group' [_ZN1XC5Ev] contains 1 sections:
   [Index]    Name
   [   14]   .text._ZN1XC2Ev

(这是 GCC 4.6,这可能是它与上面的定义不匹配的原因)

于 2014-09-25T14:11:11.427 回答