GNU GCC (g + +) : 为什么它会生成多个 dtor?

开发环境: GNU GCC (g + +)4.1.2

当我试图研究如何在单元测试中增加“代码覆盖率——特别是函数覆盖率”时,我发现一些类 dtor 似乎被多次生成。你们有人知道为什么吗?

通过使用下面的代码,我尝试并观察了上面提到的内容。

在“ test.h”里

class BaseClass
{
public:
~BaseClass();
void someMethod();
};


class DerivedClass : public BaseClass
{
public:
virtual ~DerivedClass();
virtual void someMethod();
};

在“ test.cpp”中

#include <iostream>
#include "test.h"


BaseClass::~BaseClass()
{
std::cout << "BaseClass dtor invoked" << std::endl;
}


void BaseClass::someMethod()
{
std::cout << "Base class method" << std::endl;
}


DerivedClass::~DerivedClass()
{
std::cout << "DerivedClass dtor invoked" << std::endl;
}


void DerivedClass::someMethod()
{
std::cout << "Derived class method" << std::endl;
}


int main()
{
BaseClass* b_ptr = new BaseClass;
b_ptr->someMethod();
delete b_ptr;
}

当我构建上面的代码(g + + test.cpp-o test)并查看生成的符号类型如下所示时,

Nm ——爆破试验

我可以看到以下输出。

==== following is partial output ====
08048816 T DerivedClass::someMethod()
08048922 T DerivedClass::~DerivedClass()
080489aa T DerivedClass::~DerivedClass()
08048a32 T DerivedClass::~DerivedClass()
08048842 T BaseClass::someMethod()
0804886e T BaseClass::~BaseClass()
080488f6 T BaseClass::~BaseClass()

我的问题如下。

1)为什么生成了多个 dtor (BaseClass-2,DeridedClass-3) ?

2)这些医生之间有什么区别? 如何选择性地使用这些多名医生?

我现在有一种感觉,为了实现 C + + 项目100% 的函数覆盖率,我们需要理解这一点,这样我就可以在单元测试中调用所有这些 dtor。

如果有人能就上述问题给我答复,我将不胜感激。

8481 次浏览

First, the purposes of these functions are described in the Itanium C++ ABI; see definitions under "base object destructor", "complete object destructor", and "deleting destructor". The mapping to mangled names is given in 5.1.4.

Basically:

  • D2 is the "base object destructor". It destroys the object itself, as well as data members and non-virtual base classes.
  • D1 is the "complete object destructor". It additionally destroys virtual base classes.
  • D0 is the "deleting object destructor". It does everything the complete object destructor does, plus it calls operator delete to actually free the memory.

If you have no virtual base classes, D2 and D1 are identical; GCC will, on sufficient optimization levels, actually alias the symbols to the same code for both.

There are usually two variants of the constructor (not-in-charge / in-charge) and three of the destructor (not-in-charge / in-charge / in-charge deleting).

The not-in-charge ctor and dtor are used when handling an object of a class that inherits from another class using the virtual keyword, when the object is not the complete object (so the current object is "not in charge" of constructing or destructing the virtual base object). This ctor receives a pointer to the virtual base object and stores it.

The in-charge ctor and dtors are for all the other cases, i.e. if there is no virtual inheritance involved; if the class has a virtual destructor, the in-charge deleting dtor pointer goes into the vtable slot, while a scope that knows the dynamic type of the object (i.e. for objects with automatic or static storage duration) will use the in-charge dtor (because this memory should not be freed).

Code example:

struct foo {
foo(int);
virtual ~foo(void);
int bar;
};


struct baz : virtual foo {
baz(void);
virtual ~baz(void);
};


struct quux : baz {
quux(void);
virtual ~quux(void);
};


foo::foo(int i) { bar = i; }
foo::~foo(void) { return; }


baz::baz(void) : foo(1) { return; }
baz::~baz(void) { return; }


quux::quux(void) : foo(2), baz() { return; }
quux::~quux(void) { return; }


baz b1;
std::auto_ptr<foo> b2(new baz);
quux q1;
std::auto_ptr<foo> q2(new quux);

Results:

  • The dtor entry in each of the vtables for foo, baz and quux point at the respective in-charge deleting dtor.
  • b1 and b2 are constructed by baz() in-charge, which calls foo(1) in-charge
  • q1 and q2 are constructed by quux() in-charge, which falls foo(2) in-charge and baz() not-in-charge with a pointer to the foo object it constructed earlier
  • q2 is destructed by ~auto_ptr() in-charge, which calls the virtual dtor ~quux() in-charge deleting, which calls ~baz() not-in-charge, ~foo() in-charge and operator delete.
  • q1 is destructed by ~quux() in-charge, which calls ~baz() not-in-charge and ~foo() in-charge
  • b2 is destructed by ~auto_ptr() in-charge, which calls the virtual dtor ~baz() in-charge deleting, which calls ~foo() in-charge and operator delete
  • b1 is destructed by ~baz() in-charge, which calls ~foo() in-charge

Anyone deriving from quux would use its not-in-charge ctor and dtor and take on the responsibility of creating the foo object.

In principle, the not-in-charge variant is never needed for a class that has no virtual bases; in that case, the in-charge variant is then sometimes called unified, and/or the symbols for both in-charge and not-in-charge are aliased to a single implementation.