c++动态内存开辟

首先,来谈一谈内存管理:
来看一道很有意思的题目:


int i = 1;
static int j = 1;//i和j都在数据段(静态区),但链接属性不一样,static在外部文件不可见

void test1()
{
    static int k = 1;//j和k都是static,生命周期一样,但作用域不一样,static不改变变量的作用域
    int n = 1;//k和n的生命周期不一样,作用域一样
}

void test()
{
                                        //sizeof()      strlen()
    int a[10] = { 1, 2, 3, 4 }; //数组        12          
    char a2[] = "abcd";         //数组        5               4
    char* a3 = "abcd";          //指针        4               4
    //a2[0]在栈,a3[0]在代码段(常量区)
}

这是我的分析:
这里写图片描述

在c语言里,如何理解:


void test()
{
    int* p1 = (int*)malloc(sizeof(int)*4);
}

这里写图片描述

接下来进入正题,c++用 new/delete 来实现动态内存开辟:

void test1()
{
    int* p1 = new int;
    int* p2 = new int(3);
    int* p3 = new int[3];

    delete p1;
    delete p2;
    delete[] p3;
}

这里写图片描述

注意,malloc/free new/delete new[]/delete[]一定要cp使用,一定要cp。
为什么要cp?malloc/free和new/delete之间有什么关系和差异呢?
首先,malloc/free是库函数,new/delete是自定义类型,而且和构造函数,析构函数有关,那么我写一个简单的SeqList类来说明问题:

class Seqlist
{
public:
    Seqlist(size_t n = 1)
        :_a(new int[n])
    {
        cout << "构造函数" << endl;
    }

    ~Seqlist()
    {
        cout << "析构函数" << endl;
        delete[] _a;
    }

private:
    int* _a;
};

这里写图片描述
可以看出,new是在创建对象的同时还会调用构造函数,delete清理对象的同时还会调用析构函数。

    SeqList* p2 = (SeqList*)malloc(sizeof(SeqList) * 3);
    free(p2);

然后malloc和free不会调用,只是开辟空间和清理。
所以说,不匹配使用时极容易出错。比如:

    SeqList* p1 = new SeqList[3];//调用三次构造函数
    free(p1);

这里new调用了三次构造函数,但是没有和delete匹配使用,导致没有释放_a指针,存在内存泄漏。
具体分析如下:
这里写图片描述
所以p1应该这样释放:
delete[] p1;//调用三次析构函数
内存泄漏存在很大危害,这个我在研究。

当我调试的时候,new语句跳转时会出现一个函数:operator new,
这里写图片描述
同样的,delete语句也会跳转到operator delete
这里先声明一点,operator new与operator delete不是重载,它们是函数。
其实operator new是malloc的一层封装.
我们知道,malloc失败了返回0,而operator new失败了会抛异常,它们处理错误方式不同。

//库函数
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
        {       // try to allocate size bytes
        void *p;
        while ((p = malloc(size)) == 0)
                if (_callnewh(size) == 0)
                {       // report no memory
                static const std::bad_alloc nomem;
                _RAISE(nomem);
                }

        return (p);
        }

可以清楚地看到封装关系。
总结:
new:1.调用operator new,operator new再调malloc分配空间。
2.调用构造函数。
delete:1.调用operator delete,operator delete再调free释放空间.
2.调用析构函数。

这里有一个问题,上面的一个代码:

SeqList* p1 = new SeqList[3];//调用三次构造函数
delete[] p1;//调用三次析构函数

既然[]里没有指定大小,那么怎么知道析构三次呢?
上文谈到:
new:1.调用operator new,operator new再调malloc分配空间。
2.调用析构函数。
调试说明问题:
这里写图片描述

这是malloc,它的res指针返回给operator new的指针p。
这里写图片描述
这里可以清楚的看到指针的地址。
但是当地址传给p1时却不是4aa0.
这里写图片描述
而是向后给了4字节,通过查阅资料,原来这四个字节才是问题的关键所在。
这里写图片描述
所以说,malloc/free new/delete new[]/delete[]一定要cp使用,一定要cp。
如果:new[] ->free,free释放返回的地址,肯定崩溃。
如果:new[] -> delete:会崩溃,因为delete会认为是new出来的,不会往前移四字节,,只析构一次,一定崩溃。
如果:new -> delete[]:会崩溃,因为delete[]会认为是new[]出来的,往前移动必然崩溃。

猜你喜欢

转载自blog.csdn.net/han8040laixin/article/details/78407506