使用对象而不是指针时双重释放或损坏

时间:2015-09-01 10:22:35

标签: c++ vector runtime-error destructor

为什么第一个代码段在调用析构函数时会导致双重释放或损坏错误,而第二个代码段工作正常?

optician.append($('<option></option>').text(OpticianList[i]));

这个有效:

int main( int argc, char** argv )
{
vector<int> vec = *new vector<int>(10);
vec.at(3) = 6;
vec.~vector(); 
}

即使析构函数被调用两次:为什么错误出现在倒数第二行(根据gbd)而不是int main( int argc, char** argv ) { vector<int> *vec = new vector<int>(10); vec->at(3) = 6; vec->~vector(); } 当对象超出范围时?

4 个答案:

答案 0 :(得分:2)

这个vector<int> vec = *new vector<int>(10);实际上创建了两个向量。第一个是由new vector<int>(10)创建的。然后使用复制构造函数创建第二个vec。 第一个永远不会被摧毁。第二个被销毁两次,通过手动调用析构函数并在它超出范围时自动调用。

答案 1 :(得分:1)

第一种情况:

vector<int> vec = *new vector<int>(10);

这里发生了三件事:

  1. 您动态分配矢量。 new返回一个指针。在这种情况下,它是一个临时变量,没有名称。
  2. 您取消引用此指针并获取 rvalue
  3. 构造另一个对象vec,使用上一步中的右值初始化它。这有效地调用了复制构造函数vector(const vector&)
  4. 结果,有两个向量。第一个是堆中的某个地方,你没有指向它的指针。这是内存泄漏。然后,有一个自动持续时间对象vec。两种载体都具有相同的含量。

    vec.~vector();
    

    这里显式调用自动持续时间对象的析构函数。你几乎不需要这样做。这主要用于实现 placement new

    一旦离开范围(例如离开函数体),析构函数就会再次被自动调用。因此,您将获得双重免费错误。

    第二种情况:

    vector<int> *vec = new vector<int>(10);
    vec->~vector();
    

    在这里你破坏对象(即调用析构函数),但是不要释放它占用的内存。因此,您仍然有内存泄漏。但是,由于动态对象在离开作用域时不会自动销毁,因此不会发生双重错误。

    您应该使用delete vec;来销毁动态分配的向量。它将调用析构函数并释放内存。

答案 2 :(得分:1)

让我们逐行检查代码。计划一:

vector<int> vec = *new vector<int>(10);

向量vec在=左侧定义。另一个未命名的向量是在堆上的=右侧创建的。请注意,这涉及两个免费商店分配:一个用于(小)矢量对象本身,另一个用于其中的数据,10个整数。 new返回的地址不会保留在任何位置,但会立即取消引用,以使=右侧的表达式成为矢量对象。它用于复制初始化vec。这涉及为免费商店的vec数据分配内存,并将所有匿名向量的元素复制到其中。请注意,vec的数据与右侧矢量的数据位于不同的位置。

vec.at(3) = 6;:与讨论无关。

vec.~vector();:当初始化vec时,执行vec的析构函数将释放为免费存储上的数据分配的内存。它不会尝试释放vec的内存(这很好,因为vec不在堆上而是在堆栈上,并且当堆栈被解开时会被自动销毁,因为范围是剩下的。)

}:vec的范围结束,以便再次调用vec的析构函数(该语言不会保留破坏书,例如对象中没有“已销毁”的标记)。这是一件坏事,因为正如我们所知,~vector()试图释放为其数据分配的内存。 (它是否应该将数据指针设置为null也是有争议的,在这种情况下,多次解除分配尝试不会是灾难性的。反驳的论点是,这只会掩盖灾难性的编程错误。)

除了由于错误的显式析构函数调用而明显双重分配vec数据之外,用于vec初始化的空闲存储上的向量也永远不会被释放或销毁。 (由于地址丢失,因此无法释放。)对于功能完备的运行时环境中的POD向量,这是可以的:POD元素不需要销毁,并且当进程退出时,运行时将进程的堆返回给操作系统。但暗示很明显:如果元素需要销毁会怎样(想想现在永远不会关闭的数据库连接);并且存在独立的实现,其中内存可能不会返回到OS(哪个OS?),或者代码被用作长时间运行的服务器的一部分而无需重新考虑。

计划二:

std::vector<int> *vec = new std::vector<int>(10);

此行在=左侧定义指针 vec。右侧在免费商店中创建一个int的向量。该未命名向量的地址用于初始化指针vec。请注意,与第一个示例一样,对new的调用涉及免费存储上的两个分配:向量本身的(小)内存,以及向量数据的(单独的,大的)内存“包含”。

vec->at(3) = 6;与讨论无关。

vec->~vector();显式调用了vec的析构函数。这会释放矢量的数据,但保持矢量不变。后者是不好的,因为vec点的向量对象在免费商店中被分配,并且也应该被释放。同时执行这两个操作的正确方法是按照另一个答案的建议调用delete。 (但上面的讨论适用 - 在正常运行时,如果程序以任何方式结束,对于int向量并不重要。)

}:指针vec的范围结束,不会触发任何内容(特别是,它不会释放内存vec点,这在这里很糟糕,它不会调用vector的析构函数,这在这里很好)。请注意,智能指针的行为会有所不同,并且当它们的作用域结束时,它们会在内部保存的原始指针上调用delete。

答案 3 :(得分:0)

两个版本都包含错误,但第一个是非常糟糕的,如果您决定使用矢量对象,它应该是什么样子:

int main()
{
    std::vector<int> vec(10);
    vec.at(3) = 6;
}

这就是使用指针看起来的样子:

int main( int argc, char** argv )
{
    std::vector<int> *vec = new std::vector<int>(10);
    vec->at(3) = 6;
    delete vec;
}

最后一行甚至不需要,因为程序将终止并且ram在任何情况下都是免费的。