为什么第一个代码段在调用析构函数时会导致双重释放或损坏错误,而第二个代码段工作正常?
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();
}
当对象超出范围时?
答案 0 :(得分:2)
这个vector<int> vec = *new vector<int>(10);
实际上创建了两个向量。第一个是由new vector<int>(10)
创建的。然后使用复制构造函数创建第二个vec
。
第一个永远不会被摧毁。第二个被销毁两次,通过手动调用析构函数并在它超出范围时自动调用。
答案 1 :(得分:1)
第一种情况:
vector<int> vec = *new vector<int>(10);
这里发生了三件事:
new
返回一个指针。在这种情况下,它是一个临时变量,没有名称。vec
,使用上一步中的右值初始化它。这有效地调用了复制构造函数vector(const vector&)
。结果,有两个向量。第一个是堆中的某个地方,你没有指向它的指针。这是内存泄漏。然后,有一个自动持续时间对象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在任何情况下都是免费的。