【问题标题】:destruction of object with vector member用矢量成员销毁对象
【发布时间】:2014-12-16 22:57:09
【问题描述】:

我最近在使用向量作为成员编码类时遇到了一个奇怪的错误。这个bug是我自己引入的,但运行时的结果让我很震惊。简化示例如下所示。

基本上,该错误出现在构造函数中,其中h2 的大小仅为dimension_ 而不是dimension_*dimension_,这导致h2void A::set()double A::get() 中的访问超出范围。我承认有这样的错误是我的坏事。我认为应该发生的是void A::set() 中的h[2]h[3] 将访问一些随机内存地址或仅在向量h2 的保留范围内。但真正发生的是当调用析构函数时,会引发free() 错误,然后是很多虚拟的Backtrace,我不想在这里列出。

*** glibc detected *** ./a.out: free(): invalid next size (fast): 0x0000000001637040 ***

但如果我不访问void A::set() 中的h[3],就不会发生这种情况。我的问题是向量和析构函数到底发生了什么?向量的析构函数是否知道我访问了哪些元素?我认为向量只知道它的大小并在调用析构函数时释放内存。 任何想法将不胜感激。谢谢。

以下是显示运行时错误的示例代码。

#include<vector>
#include<cstddef>
#include<iostream>

class A
{
    public:
        A(size_t dimension);
        virtual ~A();
    public:
        virtual double get();
        virtual void set();
    protected:
        const size_t dimension_;
        std::vector<double> h1, h2;
};

A::A(size_t dimension):
    dimension_(dimension),
    h1(dimension_*dimension_),
    h2(dimension)
{}

A::~A()
{
}

double A::get()
{
    set();
    double result(0), temp(0);
    for(size_t i(0); i < dimension_; ++i)
    {
        temp = 0;
        for(size_t j(0); j < dimension_; ++j)
            {   
                temp += h1[j] * h2[j + i*dimension_];
            }   
        result += temp * h1[i];
    }
    return result;
}

void A::set()
{
    h1[0] = h1[1] = h1[2] = h1[3] = 0.5;
    h2[0] = h2[1] = h2[2] = h2[3] = 0.005;
}

int main()
{
    A mya(2);
    std::cout << mya.get() << "\n";
    return 0;
}

【问题讨论】:

    标签: c++ vector free destructor


    【解决方案1】:

    使用超出范围的参数调用向量的operator[] 会导致未定义的行为 (UB),因此从技术上讲任何事情都可能发生,包括正在发生的事情在你的情况下。一旦您调用 UB,所有赌注都将取消;您无法提前推断调用 UB 的代码的影响。

    请注意,at() 方法将进行边界检查,如果您尝试访问越界元素,则会引发异常。如果您确实访问了越界元素,您将在那个时候得到一个异常,这使得调试更容易。缺点是边界检查需要一些额外的时间,因此如果您进行数百万次向量访问,您可能会注意到at()operator[] 慢一点。是否值得权衡取舍取决于您。


    话虽如此,但很可能发生的是,您正在破坏 C++ 实现的堆分配器使用的一些簿记结构。堆分配器通常分配比请求更多的内存,并使用额外的空间来存储有关分配本身的数据。

    当向量释放用于保存向量中元素的分配时,分配器发现它的簿记数据已被破坏,因此您以这个断言结束。

    【讨论】:

    • 没想到UB可以这么深刻。我不得不说这个错误确实让我在调试时遇到了一些困难。感谢您指出at() 方法。我知道这种方法,但我也知道边界检查会降低性能,所以对于我知道大小的向量,我更喜欢operator[]
    • 感谢您的回答,这对我来说很有意义。我想我应该在调试时使用at()
    • 请注意,如果您正确编译,std::vector 的大多数实现都会进行边界检查:它是 MSC 和 g++ 的默认设置,除了优化构建,您应该使用 -D_GLIBCXX_CONCEPT_CHECKS -D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC。当您遇到边界错误时,您确实想抛出异常。
    • 感谢您的帮助。但我不太明白为什么不首选异常。我尝试使用这些标志进行编译,运行时错误消息只是告诉我有一个超出范围的订阅,并且给出了“this”的内存地址。但是,即使我使用 -g 编译,也没有关于代码哪里出错的信息。也许我错过了一些重要的东西。如果您再解释一下,那就太好了。谢谢。
    猜你喜欢
    • 2013-07-13
    • 2012-09-11
    • 2012-02-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多