• 【C++初阶】C++内存管理


    一.C/C++内存分布图

    作为C/C++方向的从业者,必须关注的四块空间:

    1. 栈(局部数据)
    2. 堆(动态申请数据)
    3. 数据段(全局数据和静态数据)
    4. 代码段(可执行代码和可读常量)

    image-20221105155959450

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    image-20221105160729104

    解析:

    1. globalVar定义在所有的函数外,所以是全局变量,位于数据区

    2. staticGlobalVar定义在函数体外[全局],且被static修饰[静态],所以是静态(全局)变量,位于数据区

    3. staticVar定义在函数体外[局部],且被static修饰[静态],所以是静态(局部)变量,位于数据区

    4. localVar定义在函数体内[局部],所以是局部变量,位于栈区

    5. num1是整型数组名,定义在函数体内[局部],所以是局部变量,位于栈区

    6. char2是字符数组名,定义在函数体内[局部],所以是局部变量,位于栈区

    7. *char2是字符数组存放的内容,位于栈区

    8. pChar3是一个指针,指向代码段中常量字符串“abcd”,定义在函数体内[局部],位于栈区

    9. *pChar3是常量字符串“abcd”,位于代码段

    10. ptr1指向动态申请的空间,定义在函数体内[局部],位于栈区

    11. *ptr1是动态申请的空间里的内容,位于堆区

    关于第7题和第9题区别:

    image-20221105163430399

    二.new和delete内存管理

    C 语言中的malloc是函数,C++中的new是关键字,操作符,都是在堆上动态申请的空间

    下面我针对内置类型和自定义类型比较new,delete和malloc,free

    1.对于内置类型

    C 语言和C++默认都没有对各自动态申请的内存进行初始化

    image-20221105164032084

    int main()
    {
    	//C语言
    	int* p1 = (int*)malloc(40);
    	free(p1);
    
    
    	//C++,默认不初始化
    	int* ptr1 = new int;
    	delete ptr1;
    	//指定初始化
    	int* ptr2 = new int(100);
    	//ptr2 = nullptr;如果后面不使用了,可以置空
    	delete ptr2;
    
    	//动态申请数组
    	//不初始化
    	int* ptr3 = new int[10];
    	delete[] ptr3;
    	//完全初始化
    	int* ptr4 = new int[10]{ 1,2,3,4,5,6,7,8,9,10 };
    	delete[] ptr4;
    	//不完全初始化
    	int* ptr5 = new int[10]{ 1,2,3,4,5 };
    	delete[] ptr5;
    
    	return 0;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    image-20221105164703648

    对于内置类型:

    new/delete相比与malloc/free,只是用法上的区别

    2.对于自定义类型(重点)

    new/delete主要是针对自定义类型设计的,对于自定义类型,

    new除了在堆上开辟空间,还会自动调用构造函数,完成对象的初始化

    delete除了在堆上释放空间,还会自动调用析构函数,完成对象的资源清理

    class A
    {
    public:
    	A(int a = 10)
    		:_a(a)
    	{
    		cout << "构造函数" << endl;
    	}
    	~A()
    	{
    		cout << "析构函数" << endl;
    	}
    private:
    	int _a;
    };
    
    int main()
    {
    	A* ptr1 = new A;
    	delete ptr1;
    	cout << "____________________________________" << endl << endl;
    	A* ptr2 = new A[4];
    	delete[] ptr2;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    image-20221105165621912

    案例:对于我们之前学过的单链表那块

    C语言:

    ListNode* BuyListNode(int val)
    {
    	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
    	newnode->_val = val;
    	newnode->_next = nullptr;
    }
    int main()
    {
        ListNode* n1 = BuyListNode(1);
    	ListNode* n2 = BuyListNode(2);
    	ListNode* n3 = BuyListNode(3);
    	n1->_next = n2;
    	n2->_next = n3;
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    C++:

    struct ListNode
    {
    	int _val;
    	ListNode* _next;
    	ListNode(int val = 0)
    		:_val(val)
    		,_next(nullptr)
    	{}
    };
    
    
    int main()
    {
    	//创建链表
    	ListNode* n1 = new ListNode(1);
    	ListNode* n2 = new ListNode(2);
    	ListNode* n3 = new ListNode(3);
    	ListNode* n4 = new ListNode(4);
    	ListNode* n5 = new ListNode(5);
    	n1->_next = n2;
    	n2->_next = n3;
    	n3->_next = n4;
    	n4->_next = n5;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    image-20221105171533535

    3.new和delete不匹配问题(了解)

    案例:不匹配现象

    	//1.
    	int* ptr1 = new int;
    	delete[] ptr1;
    
    	//2.
    	int* ptr2 = new int[10];
    	delete ptr2;
    
    	//3.
    	int* ptr3 = new int;
    	free(ptr3);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    不匹配后果:未定义,由于环境(linux还是windows或者不同编译器)不同,结果不同,不要尝试不匹配

    ps:这个问题不是内存泄漏问题(内存泄漏是不会报错的,类似一种慢性病,报错类似一种急性病)

    4.new的底层机制(了解)

    new的底层机制其实是调用operator new函数申请空间 + 调用构造函数初始化

    而operator new申请空间的底层实现也是调用malloc, 所以new的效率并没有比malloc高

    封装malloc,申请内存失败,抛异常

    封装malloc只是为了符合面向对象处理出现错误的处理方式—抛异常

    image-20221105190117608

    • 我们其实可以手动调用operator new函数

    image-20221105190435366

    ps:operator new函数的使用方式和malloc一样,唯一不同的是operator new开空间失败不会返回nullptr,而是抛异常.

    给大家看一下调用new的时候的反汇编:

    • 内置类型
    int main()
    {
    	int* a = new int;
    
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    image-20221105185939137

    这个call调用的是operator new函数

    • 自定义类型
    struct ListNode
    {
    	int _val;
    	ListNode* _next;
    	ListNode(int val = 0)
    		:_val(val)
    		,_next(nullptr)
    	{}
    };
    
    
    int main()
    {
    	//创建链表
    	ListNode* n1 = new ListNode(1);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    image-20221105185701986

    第一个call是调用operator new函数

    第二个call是调用构造函数

    同理就有operator new[]函数,调用多次operator new

    还有operator delete和operator delete[]函数

    ps:我们知道new的底层机制,但是我们没有必要使用operator new去实际编程.

    5.定位new表达式(了解)

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

    构造函数有点不一样,在我们之前学的都不能显式调用,但是定位new表达式就可以完成显式调用

    ps:析构函数可以显式调用(下图证明)

    image-20221105193313129

    class A
    {
    public:
    	A(int a = 10)
    		:_a(a)
    	{
    		cout << "构造函数" << endl;
    	}
    	~A()
    	{
    		cout << "析构函数" << endl;
    	}
    private:
    	int _a;
    };
    
    int main()
    {
    	A* ptr1 = (A*)malloc(sizeof(A));
    	if (ptr1 == nullptr)
    	{
    		perror("malloc fail");
    		exit(-1);
    	}
    	//定位New--- 对ptr1指向的这块空间,显示调用构造函数初始化
    	new(ptr1)A(1);
    
    	//ps:析构函数可以显式调用
    	ptr1->~A();
    	free(ptr1);
    	//上面两行相当于delete ptr1;
    	//上节课讲过delete等同于 调用析构函数+operator delete(失败抛异常)
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    定位new案例:

    我们听说过内存池还有池化技术,那我百度了一下,我就给大家讲一下我的理解:

    举一个例子:

    山上有好多和尚,他们每天需要来山脚下挑已经过滤好的自来水喝,

    但每一次都要排老长老长的队,于是各个和尚都在自己家里建水池蓄水,可以避免每天排队,提高效率

    于此同时也产生一个问题:蓄水池的水需要一个过滤装置定时过滤杂质后才能饮用

    上述的山脚下的自来水就类似new/malloc,挑山脚下的别人已经过滤好的纯净水就是调用new/malloc开辟空间并且开好的空间是已经初始化好的,

    于是和尚建蓄水池蓄水就是建内存池,提高效率

    内存池的水需要定时过滤就类似定位new,对内存池的空间进行初始化

    三.面试题

    1.new/delete和malloc/free的区别(理解)

    malloc/freenew/delete
    函数操作符
    对内置类型和自定义类型都不初始化对内置类型不初始化,对自定义类型初始化
    申请空间时有时类型的大小需要计算直接跟类型和个数
    返回值为void*,使用前要强转new直接返回对应的指针类型
    开辟空间失败返回null开辟空间失败抛异常

    最大的区别是new/delete对于自定义类型能够自动调用构造函数和析构函数

    2.内存泄漏

    ps:内存泄漏是指针丢了,而不是内存丢了(内存一直都在)—–-指针丢了就是找不到这块空间了

    (想想永不关闭的程序,比如后台服务器就知道危害了)

    内存泄漏指由于疏忽或者错误造成程序未能释放已经不再使用的内存的情况

    并不是指物理上的消失,而是失去了对这段内存的控制,从而造成了内存的浪费.

  • 相关阅读:
    Linux之ansible(playbook)超详解
    制售《原神》外挂非法获利200万,外挂的危害有多大?
    Other——电子产品的零售版本和OEM版本
    【linux环境下安装opencv3.4.5】
    在React中的函数组件和类组件——附带示例的对比
    达梦SQL优化:如何定位慢的SQL
    第10章 MySQL(一)
    程序员的自我修养
    “外卷”的羽绒服
    Linux - 内存 - 预留内存占用分析
  • 原文地址:https://blog.csdn.net/qq_64428099/article/details/127708175