在程序退出时,类的静态实例不能正确处理资源删除

时间:2012-10-31 23:22:49

标签: c++ linux static destructor gcc4.7

今天我在处理项目时偶然发现了这一点。 基本上,在我的项目中,我有类似于下面的资源处理。

class Resource {
public:
    static Resource instance_;
    ~Resource () {
        std::for_each(res_.begin(), res_.end(), [&] (Stuff *stuff) {
            delete stuff;
        });
    }
private:
    std::set<Stuff*> res_;
};

再次运行valgrind,程序退出时,我看到一些非常神秘的错误。 像这样:

by 0x40DD42: std::_Rb_tree<unsigned int, std::pair<unsigned int const, std::pair<Vertex*, unsigned int> >, 
std::_Select1st<std::pair<unsigned int const, std::pair<Vertex*, unsigned int> > >, std::less<unsigned int>, 
std::allocator<std::pair<unsigned int const, std::pair<Vertex*, unsigned int> > > >::equal_range(unsigned int const&)
\\ a lot, lot more to come...

通读所有这些,似乎表明Resource的析构函数正在释放已经释放的内存区域。

但我的析构函数肯定是正确处理的。为了证明这一点,我将删除代码从析构函数移到另一个成员函数中。 所以,像这样:

class Resource {
public:
    static Resource instance_;
    ~Resource () { /* does nothing */ }
    void clear () {
        std::for_each(res_.begin(), res_.end(), [&] (Stuff *stuff) {
            delete stuff;
        });
    }
// ... more
};

然后,我只是在程序退出之前在静态实例上调用clear()。 现在,错误不再出现在valgrind!

为了进一步证明这只是在程序死亡时静态实例死亡的时候,我删除了静态实例。 我没有使用静态instance_,而是在我的程序启动时在Resource中在堆栈上分配了main()的实例。 在这个改变之后,问题也消失了。

现在,我想知道为什么会这样? 这与操作系统有什么关系吗?

我的猜测是,操作系统可能会尝试在程序死亡时释放所有内容,而我的析构函数恰好在清理期间启动,而不是之前。

这里讨论的操作系统是Linux(Ubuntu 12.10)。 编译器是gcc 4.7.2。

由于

2 个答案:

答案 0 :(得分:2)

我正在运行Xubuntu 12.10,我的gcc版本与你的匹配:

$ gcc --version
gcc (Ubuntu/Linaro 4.7.2-2ubuntu1) 4.7.2

使用此测试用例:

#include <set>
#include <algorithm>

class Thing {};

class Test {
public:
    static Test test;

    std::set<Thing*> things;

    ~Test() {
        std::for_each(things.begin(), things.end(), 
                      [&](Thing* thing){ 
                          delete thing; 
                      });
    } 
};

Test Test::test;

int main() {
    Test::test.things.insert(new Thing());
    Test::test.things.insert(new Thing());

    return 0;
}

valgrind报告一切正常,4次分配,4次释放。如果您尝试相同的测试用例会发生什么?

答案 1 :(得分:0)

您忘记实现[深层]复制构造函数和关联的赋值运算符。

创建Resource实例的副本时会发生什么?第一个副本最终被破坏,然后第二个副本最终被破坏......每个副本的破坏导致相同指针的delete离子。不好。

简而言之,您忘记实施三条规则。在你的C ++书籍词汇表中查阅,因为它肯定会在那里。