添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
free(): double free detected in tcache 2

这时有读者可能会说,谁会写出这么操蛋的代码,delete两次难道不难发现?
不急,我们继续往下看。

2.拷贝构造函数

拷贝构造函数一般来说容易被忽略(本人没有大型C++项目经历,一些主观感受都是我意淫的,比如我个人容易忽略拷贝构造函数等习惯),但实际上拷贝构造函数经常在无形中会被调用。我们知道,如果一个类中不声明拷贝构造函数,则会有一个默认的拷贝构造函数。

观察一下代码

class A {
private:
    static int id_;
    int id;
public:
    A() {
        id = id_++;
        cout << id << " construct!" << endl;
    ~A() {
        cout << id << " deconstruct!" << endl;
int A::id_ = 0;
int main() {
    A a;
    A b = a;
    A c(a);

此处的A b = a 和 A c(a)其实都显式调用了拷贝构造函数,因此a b c三个对象id都是1。为了证明拷贝构造函数被调用,我们可以写一个自定义拷贝构造:

   A:: A(const A & a) {
        this->id = a.id;
        cout << id << "copy construct!" << endl;

默认拷贝构造对每个成员变量执行等号赋值操作到新的构造对象中,因此运行结果为

0 construct!
0copy construct!
0copy construct!
0 deconstruct!
0 deconstruct!
0 deconstruct!

由于abc三个对象都在栈上,可以自动回收,因此可以看到三个析构运行。

3.由于默认拷贝构造函数引发双释放

上文中我们谈到默认的拷贝构造对 要构造的对象中的成员变量执行等号赋值,其语句类似于我们上文中自定义的拷贝构造函数。这种情况下是可能导致双释放问题的!请看下面的类定义。

class A {
private:
    static int id_;
    int id;
    int * buffer;
public:
    A() {
        id = id_++;
        cout << id << " construct!" << endl;
        buffer = new int[10];
    ~A() {
        delete [] buffer;
        cout << id << " deconstruct!" << endl;

我们为A加入了一个指针类型的成员变量,用来动态申请空间;同时删掉了自定义拷贝构造函数,让系统执行默认拷贝构造。这时如果执行2中的main函数,必然报错。

int main() {
    A a;
    A b = a;
    A c(a);
0 construct!
0 deconstruct!
free(): double free detected in tcache 2

想必聪明的读者已经猜到了原因,这是因为调用了默认拷贝构造,我们有 c.buffer == b.buffer == a.buffer,这是典型的浅拷贝,也就是说abc三个对象实际上共享了buffer指向的内存空间,这时依次析构就会发生问题,因为buffer实际上只能释放一次。

4.一个更隐蔽的案例

我们修改上面的main函数,引入vector

int main() {
    vector<A> v;
    v.emplace_back(A());
    return 0;

这是一种隐蔽的情况,因为在压入的时候,A()会率先生成一个A的对象,我们姑且叫做a(实际上这个对象是匿名的,但我们在本文中给他一个名字),但实际上压入v的并不是a,而是a的一个拷贝a’(a’也是匿名的),同时由于a是匿名的,完成拷贝构造a’对象的任务后,a会立刻被回收,也就是调用a的析构函数,这时a的buffer被释放。当然a’的buffer也同时被释放了,但很多开发者到此为止还没有意识到这件事。事实上a’的buffer很可能仍然可用,但不能保证内存安全。

上述过程可以通过运行结果看出

0 construct!
0 deconstruct!
free(): double free detected in tcache 2

解决方案:
我们修改析构函数,不要释放buffer就好啦~

    A::~A() {
//        delete [] buffer;
        cout << id << " deconstruct!" << endl;

哈哈哈哈开个玩笑,其实我也没想到好的解决方案。如果我们 重写拷贝构造函数,将buffer的拷贝改成深拷贝,对于vector的使用,显然多此一举,有损效率。
一种方案是通过指针调用。

int main() {
    vector<A*> v;
    v.emplace_back(new A());
    return 0;

但这种方法,v中的指针不能自动释放。

5.真正的解决方案

实际上为了解决这个问题,新版的vector已经加入了新方法emplace_back,这个方法与其模板类的构造方法共享参数列表。举个例子

#include <iostream>
#include <vector>
using namespace std;
class A {
private:
    static int id_;
    int id;
    int * buffer;
public:
    A(int id) {
        this->id = id;
        cout << this->id << " construct!" << endl;
        buffer = new int[10];
    A(const A & a){
        this->id = a.id;
        this->buffer = a.buffer;
        cout << this->id << "copy construct!" << endl;
    int get_id(){
        return this->id;
    ~A() {
        delete [] buffer;
        cout << id << " deconstruct!" << endl;
int A::id_ = 0;
int main() {
    vector<A> v;
    v.reserve(10);
    v.emplace_back(10);
    v.emplace_back(15);
    v.emplace_back(20);
    cout << v[0].get_id() << ' ' << v[0].get_id() << endl;
    return 0;

上述代码中的emplace_back相当于调用了A的带参构造函数(参数为id),这样避免了拷贝构造的发生。但同时我们要提防vector。由于vector是动态size,我们必须先reserve一定大小的空间,否则当vector 占用空间达到其capacity,就会发生拷贝构造,导致前述问题。 如何解决,欢迎讨论。

1. 介绍我们先从一段平常的代码说起int main() { auto * p = new int [10]; delete [] p; return 0;}申请一段空间,并释放,没有任何问题。再看下面的代码int main() { auto * p = new int [10]; delete [] p; delete [] p; return 0;}这样就会出问题,这是一个runtime error,成为双释放(double f
私有构造函数 私有构造函数是一种特殊的实例构造函数。它通常用在只包含静态成员的类中。如果类具有一个或多个私有构造函数而没有公共构造函数,则其他类(除嵌套类外)无法创建该类的实例。例如: class NLog // Private Constructor: private NLog() { } public static double e = Math.E; //2.71828... 声明空构造函数可阻止自动生成默认构造函数。注意,如果您不对构造函数使用访问修饰符,则在默认情况下它仍为私有构造函数。但是,通常显式地使用 private 修饰符来清楚地表明该类不能被实例化。
CTP开发中,如果把Trade,Market的so放在一起开发,如果不做处理,会遇到double free or corruption(!prev)的错误,基本如下: *** glibc detected *** ./bin/quant_ctp_XTrader_no_debug_2017-03-16_15-36-20: double free or corruption (!prev): 0x0000000001d71120 *** ======= Backtrace: ========= /lib64/libc.so.6[0x32b4a75f3e] /lib64/libc.so.6[0x32b4a78dd0] /usr/lib64/libthosttraderapi.so(+0x184612)[0x7f3d1e503612] /lib64/libc.so.6(__cxa_finalize+0x9d)[0x32b4a35e7d] /usr/lib64/libthosttraderapi.so(+0x104b46)[0x7f3d1e483b46] 开发中我也遇到该问题,后辛苦找到解决办法。方案附送在该附件里,仅供参考。 仅供参考!仅供参考!仅供参考! 重要的事情说三遍! 使用此代码引起的任何损失,笔者不承担任何责任。
std::vector源码分析vector容器概述一、vector对比array容器二、底层技术实现1.空间的动态扩展2.迭代器相关三、源码摘要 vector容器概述 // highlighted block 1. 模板类vector类似于string类,也是一种动态数组。 2. 它可以在运行阶段的设置vector对象的长度,可在末尾附加新数据,还可以在中间插入新数据。 3. 它是使用new创建动态数组的替代品。 实际上vector类确实使用new和delete来管理内存,但这种工作是自动完成的。 先说一下浅拷贝和深拷贝: C++中默认构造函数主要有两类, 第一类是针对定义类对象的,定义类对象时,如果没有对应的构造函数,会默认调用一个函数体为空的无参构造函数,比较简单,本文不多赘述; 第二类是针对类对象初始化新类对象的,当用类对象初始化新的类对象时,如果没有对应的拷贝构造函数,会调用一个默认的拷贝构
为了解决函数不能重名的问题,就好比有两本一模一样的书,为了区分书是谁的在上面分别写上了不同的名字,而这个名字就是命名空间。 而c++中的输入是用流的方式实现的 键盘—&gt;”hello”—&gt;cin(输入流)—&gt; &gt;&gt;(提取)—&gt;变量—&gt; &lt;&lt;(插入) —&gt; cout(输出流)—&gt;屏幕 while(cin &gt;&g...
设计一个Ear类,描述狗的耳朵,包含属性color(int型)。再设计一个Dog类,包含两个Ear成员,leftEar和rightEar,并包含狗的体重weight(double型)和年龄age(int型)。 编写Ear类和Dog类的构造函数、拷贝构造函数和析构函数以及其它必要的函数。在构造函数、拷贝构造函数和析构函数中输出“调用XX类的XX函数”。 测试上述类。
int age; public: Dog(int lc, int rc, double w, int a) : leftEar(lc), rightEar(rc), weight(w), age(a) { cout << "调用Dog类的构造函数" << endl; Dog(const Dog& d) : leftEar(d.leftEar), rightEar(d.rightEar), weight(d.weight), age(d.age) { cout << "调用Dog类的拷贝构造函数" << endl; ~Dog() { cout << "调用Dog类的析构函数" << endl; 测试代码如下: ```c++ int main() { Ear e1(1); Ear e2(e1); Dog d1(2, 3, 10.5, 3); Dog d2(d1); return 0; 输出结果如下: 调用Ear类的构造函数 调用Ear类的拷贝构造函数 调用Ear类的构造函数 调用Ear类的构造函数 调用Dog类的构造函数 调用Ear类的拷贝构造函数 调用Ear类的拷贝构造函数 调用Dog类的拷贝构造函数 调用Dog类的析构函数 调用Ear类的析构函数 调用Ear类的析构函数 调用Dog类的析构函数 调用Ear类的析构函数 调用Ear类的析构函数 可以看到,构造函数、拷贝构造函数和析构函数都被正确地调用了。