C++动态内存管理

目录

C/C++内存分配

C++内存管理

C++内存管理介绍

C++内存管理使用

C++内存管理基本语法

operator new 和 operator delete函数

定位new表达式(placement-new)

基本语法

使用场景

malloc/free和new/delete

相同点

不同点


C/C++内存分配

在C语言动态内存管理章节已经了解到内存的分类,包括下面四个区域:

  1. 栈又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。
  2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。
  3. 堆用于程序运行时动态内存分配,堆是可以上增长的。
  4. 数据段--存储全局数据和静态数据。
  5. 代码段--可执行的代码/只读常量
int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
    static int staticVar = 1;
    int localVar = 1;
    int num1[10] = { 1, 2, 3, 4 };
    char char2[] = "abcd";
    const char* pChar3 = "abcd";
    int* ptr1 = (int*)malloc(sizeof(int) * 4);
    int* ptr2 = (int*)calloc(4, sizeof(int));
    int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
    free(ptr1);
    free(ptr3);
}

在上面代码中,globalVar为全局变量,与静态变量staticGlobalVarstaticGlobalVar,存放在数据段(静态区),num1char2均为局部数组,所以均放在内存的栈区。对于指针类型pChar3ptr1ptr2ptr3均为局部变量,所以均存放在栈区,但是pChar3指向的内容是字符串的第一个元素的地址,该地址在内存的代码段(常量区),其余三个指针均指向在内存堆区开辟的空间

📌

注意尽管char2中存储的是字符串,但是该字符串只是存放在代码段区域的字符串的拷贝,所以依旧在栈区

C++内存管理

C++内存管理介绍

在C语言中,使用malloc/calloc/realloc进行动态内存空间的开辟,但是这三种方式中只有calloc会对数据区域进行初始化,并且初始化值为0,并且使用free宏对动态分配的空间进行释放

#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int main()
{
    int* ptr = (int*)malloc(sizeof(int) * 2);
    assert(ptr);
    int* ptr1 = (int*)calloc(2, sizeof(int));
    assert(ptr1);
    int* tmp = (int*)realloc(ptr, sizeof(int) * 20);
    assert(tmp);
    //此处不需要释放ptr原来指向的空间
    ptr = tmp;
    free(ptr);
    free(ptr1);

    return 0;
}

在C++中,仍然可以使用malloc/calloc/realloc进行动态内存分配和free进行空间释放,但是更推荐使用new进行动态内存分配以及delete进行空间释放,所以上面的代码可以转化为下面的代码

#include <iostream>
using namespace std;

int main()
{
    int* ptr = new int[2];
    int* ptr1 = new int[2] {0};

    delete[] ptr;
    delete[] ptr1;

    return 0;
}

C++内存管理使用

C++内存管理基本语法

在C++中,使用new关键字进行内存空间的申请

//单个空间开辟
指定类型的指针 = new 需要开辟的空间中的数据类型;
指定类型的指针 = new 需要开辟的空间中的数据类型(初始值);
//连续空间开辟
指定类型的指针 = new 需要开辟的空间中的数据类型[需要开辟的空间个数];
指定类型的指针 = new 需要开辟的空间中的数据类型[需要开辟的空间个数]{每一个空间初始化值};

在C++中,使用delete关键字进行内存空间的释放

//单个空间释放
delete 指向需要释放的空间的指针;
//连续空间释放
delete[] 指向需要释放的空间的指针;

📌

注意,newdelete一定要匹配使用,单个空间开辟就使用单个空间的释放,连续空间的开辟就使用连续空间的释放,更不能newfree等交叉使用

对于内置类型来说,如下面代码

#include <iostream>
using namespace std;

int main()
{
    //单个空间开辟和释放
    int* ptr = new int;//不初始化
    cout << *ptr << endl;
    int* ptr1 = new int(10);//初始化为10
    cout << *ptr1 << endl;
    delete ptr;
    delete ptr1;

    //连续空间开辟和释放
    int* ptr2 = new int[5];//不初始化
    int* ptr3 = new int[5] {1, 2, 3, 4, 5};//初始化
    for (int i = 0; i < 5; i++)
    {
        cout << ptr2[2] << " ";
    }
    cout << endl;
    for (int i = 0; i < 5; i++)
    {
        cout << ptr3[i] << " ";
    }

    delete[] ptr2;
    delete[] ptr3;

    return 0;
}
输出结果:
-842150451
10
-842150451 -842150451 -842150451 -842150451 -842150451
1 2 3 4 5

对于内置类型来说,如果使用free进行释放时,并不会出现报错

#include <iostream>
using namespace std;

int main()
{
    //单个空间开辟和释放
    int* ptr = new int;//不初始化
    cout << *ptr << endl;
    int* ptr1 = new int(10);//初始化为10
    cout << *ptr1 << endl;
    free(ptr);
    free(ptr1);

    //连续空间开辟和释放
    int* ptr2 = new int[5];//不初始化
    int* ptr3 = new int[5] {1, 2, 3, 4, 5};//初始化
    for (int i = 0; i < 5; i++)
    {
        cout << ptr2[2] << " ";
    }
    cout << endl;
    for (int i = 0; i < 5; i++)
    {
        cout << ptr3[i] << " ";
    }

    free(ptr2);
    free(ptr3);

    return 0;
}
输出结果:
-842150451
10
-842150451 -842150451 -842150451 -842150451 -842150451
1 2 3 4 5

尽管也正常输出,但是依旧不建议使用freenew开辟的空间进行释放

对于自定义类型来说,分两种情况:

  1. 自定义类型中的成员变量仅为内置类型
  2. 自定义类型中的成员变量有指针指向的动态内存分配的空间
#include <iostream>
using namespace std;

//成员变量仅为内置类型
class test
{
private:
    int _num;
public:
    test(int num = 1)
        :_num(num)
    {
        cout << "test()" << endl;
    }
    ~test()
    {
        cout << "~test()" << endl;
    }
};

int main()
{
    test* ptr = new test;
    cout << endl;
    test* ptr1 = new test(2);
    cout << endl;
    test* ptr2 = new test[5]{ 1,2,3,4,5 };
    cout << endl;
    delete ptr;
    cout << endl;
    delete ptr1;
    cout << endl;
    delete[] ptr2;

    return 0;
}
输出结果:
test()

test()

test()
test()
test()
test()
test()

~test()

~test()

~test()
~test()
~test()
~test()
~test()

在上面的代码中,对于单个对象空间的分配时,会调用一次构造函数,而对连续空间的对象空间分配时,会调用对象个数次构造函数。对于空间释放,当只有一个对象时,只调用一次析构函数,当有连续空间的对象时,则会调用对象个数次析构函数

如果将上面代码中的delete改为free,则不会调用析构函数

#include <iostream>
using namespace std;

//成员变量仅为内置类型
class test
{
private:
    int _num;
public:
    test(int num = 1)
        :_num(num)
    {
        cout << "test()" << endl;
    }
    //~test()
    //{
    //    cout << "~test()" << endl;
    //}
};

int main()
{
    test* ptr = new test;
    cout << endl;
    test* ptr1 = new test(2);
    cout << endl;
    test* ptr2 = new test[5]{ 1,2,3,4,5 };
    cout << endl;
    free(ptr);
    cout << endl;
    free(ptr1);
    cout << endl;
    free(ptr2);

    return 0;
}
输出结果:
test()

test()

test()
test()
test()
test()
test()
但是此时编译器会报警告:
“ptr”使用“new”分配,但使用“free”删除
“ptr2”使用“new []”分配,但使用“free”删除
“ptr1”使用“new”分配,但使用“free”删除

在上面的代码中,使用new为自定义类型的对象开辟空间时会调用构造函数,但是释放空间时使用free编译器会给出警告,如果没有删除析构函数,那么程序将运行终止,所以不要使用freedelete交叉使用

另外对于下面的代码来说

#include <iostream>
using namespace std;

//成员变量仅为内置类型
class test
{
private:
    int _num;
public:
    test(int num = 1)
        :_num(num)
    {
        cout << "test()" << endl;
    }
    ~test()
    {
        cout << "~test()" << endl;
    }
};

int main()
{
    test* ptr = new test;
    cout << endl;
    test* ptr1 = new test(2);
    cout << endl;
    test* ptr2 = new test[5]{ 1,2,3,4,5 };
    cout << endl;
    delete ptr;
    cout << endl;
    delete ptr1;
    cout << endl;
    delete ptr2;

    return 0;
}

因为ptr2指向的空间是连续的对象空间,此时使用对单个空间释放形式的delete时,程序也会终止运行,所以单个空间开辟使用单个空间释放,多个空间开辟使用多个空间释放

#include <iostream>
using namespace std;

//对应有资源申请的内置类型来说
class test
{
private:
    int* _a;
public:
    test()
        :_a(nullptr)
    {
        _a = new int[5];
        cout << "test()" << endl;
    }

    ~test()
    {
        delete[] _a;
        cout << "~test()" << endl;
    }

};

int main()
{
    //单个空间开辟和释放
    test* ptr = new test;
    cout << endl;
    delete ptr;
    cout << endl;

    //多个空间开辟和释放
    test* ptr1 = new test[5];
    cout << endl;
    delete[] ptr1;
    cout << endl;

    return 0;
}
输出结果:
test()

~test()

test()
test()
test()
test()
test()

~test()
~test()
~test()
~test()
~test()

在上面的代码中,类成员变量的类型为指针类型,在构造函数中,为该指针分配了5个连续的int类型空间,在析构函数中,释放这5个连续的空间。当类实例化对象时,对于单个空间的开辟和释放会调用一次构造函数和一次析构函数,而对于连续空间的开辟和释放会调用连续空间个数次的构造函数和析构函数

#include <iostream>
using namespace std;

//对应有资源申请的内置类型来说
class test
{
private:
    int* _a;
public:
    test()
        :_a(nullptr)
    {
        _a = new int[5];
        cout << "test()" << endl;
    }

    ~test()
    {
        delete[] _a;
        cout << "~test()" << endl;
    }

};

int main()
{
    //单个空间开辟和释放
    test* ptr = new test;
    cout << endl;
    free(ptr);
    cout << endl;

    return 0;
}

对于单个空间的释放来说,不会出现程序崩溃,尽管没有调用析构函数释放类对象成员变量指向的内存空间,但是当程序结束时,该空间会得到释放,此时也即内存泄漏

#include <iostream>
using namespace std;

//对应有资源申请的内置类型来说
class test
{
private:
    int* _a;
public:
    test()
        :_a(nullptr)
    {
        _a = new int[5];
        cout << "test()" << endl;
    }

    ~test()
    {
        delete[] _a;
        cout << "~test()" << endl;
    }

};

int main()
{
    //多个空间开辟和释放
    test* ptr1 = new test[5];
    cout << endl;
    free(ptr1);
    cout << endl;

    return 0;
}

对于多个连续空间的释放来说,因为free只能释放一个空间,如果直接理解会认为释放掉了连续空间中的第一个空间而并未释放后面的空间导致程序内存泄漏错误。但是实际上并不是,因为内存泄漏只有在没有足够内存时才会导致程序崩溃,编译器是不会检查内存泄露的。而真正导致程序崩溃的原因是VS在此处做出的优化

而之所以free释放空间时会报错,就是因为返回的起始位置并不是开辟的空间的真正起始位置从而导致程序崩溃,free释放空间时必须从开辟的空间的实际起始位置开始,而不能释放部分空间,而delete(不是delete[])的底层设计也是free(只是做了一些异常检查),所以调用delete而不是delete[]也会导致程序崩溃,而delete[]不崩溃是因为在释放连续空间时会先向前移动找到存储空间个数的值,再通过该值确定调用析构函数的次数,最后销毁指向连续空间的指针

总结:

对于内置类型来说,因为内置类型开辟的空间如果使用free也是直接释放指针指向的空间,只是不会调用析构函数,一般情况下不会出现问题,但是不推荐使用free。建议对单个空间用delete,连续空间用delete[]

对于自定义类型来说,使用free比较容易产生内存泄漏,所以不论是成员变量是内置类型,还是成员变量是涉及资源申请的,都建议使用deletedelete[]

operator new 和 operator delete函数

newdelete是用户进行动态内存申请和释放的操作符,operator newoperator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间

operator new 实际也是通过malloc来申请空间,operator delete 最终是通过free来释放空间的

📌

注意operator newoperator delete函数不是运算符重载

内置类型:

对于operator new函数来说

如果是单个空间,那么在申请空间时使用new,底层会调用malloc函数开辟单个空间,但是不同于malloc函数,operator new如果空间申请失败会抛出异常,而不是返回空指针

如果是连续空间,则在申请空间时new[],底层会调用malloc函数开辟连续的空间

对于operator delete函数来说

如果是单个空间,那么在释放空间时使用delete,底层会调用free函数释放单个空间,但是不同于free宏,operator delete会在释放时检查一些可能存在的问题

如果是连续空间,则在申请空间时delete[],底层会调用free函数开辟连续的空间

自定义类型:

对于operator new函数来说

如果是单个空间,则在申请空间时使用new,会调用operator new申请空间,再在申请的空间上调用构造函数创建对象

如果是多个连续空间,则在申请空间时使用new[],会调用operator new申请连续的空间,再在申请的连续空间上调用申请空间个数次的构造函数创建对象

对于operator delete函数来说

如果是单个空间,则在释放空间时使用delete,释放空间时先调用类的析构函数,再调用operator delete函数释放指向单个存储对象空间的指针

如果是连续空间,则在释放空间时使用delete[],释放空间时先调用类的析构函数,调用次数为创建的对象的个数,最后调用operator delete函数释放指向存储对象的连续空间的指针

定位new表达式(placement-new)

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象

基本语法

new (开辟空间的起始地址) 类型或者new (开辟空间的起始地址) 类型(初始值)

使用场景

定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显式调构造函数进行初始化

#include <iostream>
using namespace std;

class A
{
public:
    A(int a = 0)
        : _a(a)
    {
        cout << "A():" << this << endl;
    }
    ~A()
    {
        cout << "~A():" << this << endl;
    }
private:
    int _a;
};

int main()
{
    // p1现在指向的只不过是A类大小的一段空间,还不能算是一个对象,因为构造函数没有执行
    A* p1 = (A*)malloc(sizeof(A));
    // 在空间中调用构造函数创建对象
    new(p1)A;  // 注意:如果A类的构造函数有参数时,此处需要传参
    p1->~A();// 析构函数可以显式调用
    free(p1);
    A* p2 = (A*)operator new(sizeof(A));// 也可以直接使用operator new函数开辟空间
    // 在空间中调用构造函数创建对象
    new(p2)A(10);
    p2->~A();
    delete p2;
    return 0;
}

malloc/free和new/delete

相同点

malloc/freenew/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放

不同点

  1. mallocfree是函数,newdelete是操作符
  2. malloc申请的空间不会初始化,new可以初始化
  3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可, 如果是多个对象,[]中指定对象个数即可
  4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
  5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
  6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理
  • 25
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

怡晗★

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值