c++ —— 内存管理

发布于:2025-06-08 ⋅ 阅读:(14) ⋅ 点赞:(0)

在这里插入图片描述

1、区域划分代码示例分析

首先C++中的内存分布和C语言一样,主要可以分为栈区(也叫堆栈区)、堆区、静态区、常量区。
栈区:主要是存储非静态的局部变量,即没有被static关键修饰的局部变量、函数的参数、返回值等。
堆区:主要是程序在运行时候动态开辟的一些空间,即malloc\calloc\realloc开辟的空间。
静态区:主要是存储全局变量的数据和静态变量的数据。
常量区:只读的常量数据。

#include<iostream>
using namespace std;

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);
}

在这里插入图片描述

根据上面代码提供的各种变量和数据,给一下变量划分区域,区域有:A:栈、 B:堆、 C:静态区、D:常量区。
首先,全局变量数据是存储在数据段的。及静态区,因此
globalVar在:C 因为是在全局定义的。
staticGolbalVar在:C 因为是被static关键修饰的。
在函数Test里面的为一个局部区域,因此
staticVar在:C 因为被static修饰了。
localVar在:A 因为是非静态的局部变量。
num1在:A 因为是局部变量,开辟的连续空间在栈中。
char2和*char2在:A 首先内容是"abcd\0",它是在常量区的,而由于是定义的char[]数组,是在栈上的,当把数据”ABC\0“给到char2[]的时候,会对数据内容进行拷贝,存储到栈中的;char2是对数组名解引用,此时数组名代表首元素地址,因此结果是a,还是在栈上的,因为是做的拷贝。
pChar3在:A 因为只是一个局部变量,类型是指针类型。
解引用pChar3在:D 此时就不是在栈上了,和数组char2区别在于,pChar3是指针,不对常量区的内容做拷贝,而是指向常量区的,因此对pChar3做解引用,得到的就是’abcd\0’该内容,是在常量区的。
ptr1在:A 因为是一个局部变量,类型也是指针。
ptr1在:B 和pChar3原因一样,ptr1是指针变量,做解引用后,就是具体的内容,而内容是malloc开辟的,动态开辟的在堆区,因为
ptr1在堆区。

2、C语言中内存管理方式malloc\calloc\realloc\free

malloc:是开辟一段未初始化的内存空间;
calloc:是开辟一段已经初始化的内存空间,初始化为0;
realloc:是对之前开辟的空间作扩容的,对于新扩展的空间是不作初始化的;

// 使用 malloc 分配内存
int* ptr = (int*)malloc(n * sizeof(int));
if (ptr == NULL) {
    // 处理错误
}

// 使用 calloc 分配并初始化内存
int* arr = (int*)calloc(10, sizeof(int));

// 使用 realloc 调整内存大小
arr = (int*)realloc(arr, 20 * sizeof(int));
if (arr == NULL) {
    // 处理错误
}

sizeof(int)是每个空间的大小,对于malloc只需要传入总空间,即n个,每个空间sizeof(int);对于calloc()参数相当于把malloc参数做拆分的,但是会作初始化,初始化为0;realloc()有两个参数,第一个就是对原来的那个空间作扩容,第二个就是扩多大,realloc扩容可以分为原地扩容和异地扩容,原地扩容就是原来开辟空间有多余的,就直接往后面扩展;若剩余空间不够了,就新开辟一段空间,把原空间的数据拷贝到新空间,同时会对原空间作释放。
通过上面描述,申请一段空间,又要传入单个空间的大小和总共需要多大空间,还要对返回值做判断,是否申请成功。因此在C++里面,对内存管理方式做了改变,让用户使用起来更方便。

3、C++内存管理方式new/delete

当然在C++中,仍然可以使用C语言的那一套内存管理方式,只是相对来说比较麻烦。
对于new/delete可以分为对内置类型和自定义类型的相关操作

3.1、new/delete操作内置类型

比如想申请一个int 类型大小的空间,就可以写为如下:
申请单个类型的空间

int* ptr1 = new int;//即动态申请一个int类型的大小空间

在申请空间的同时还可以初始化数据:

int* ptr2 = new int(10);//申请一个int类型的空间,初始化为10

若想申请不止一个int类型大小的空间的话,想申请多个大小为int类型大小的空间:
申请多个连续的空间

int* ptr3 = new int[6];//即申请了6个大小空间为int类型的
int* ptr3 = new int[6]{1, 2, 3, 4};//可以同时初始化值,剩下的默认就是0。

在用对应方式申请后,需要使用delete释放该空间:

delete ptr1;
delete ptr2;
delete[] ptr3;//申请的时候是[]申请的,释放也要就[]

new和delete:是申请和释放单个内存空间的。
new[]和delete[]:是申请和释放连续内存空间的。

3.2、new/delete操作自定义类型

操作自定义类型,也就是用户自定义个数据类型,然后通过newdelete进行内存的管理,当通过下面代码观察后,其实new和delete不只是开辟空间,并且还调用构造函数和析构函数了:

class my_num
{
public:
	my_num()
	:_num(1)
	{
		cout << "my_num()构造函数……" << endl;
	}

	~my_num() 
	{
		cout << "~my_num()析构函数……" << endl;
	}

private:
	int _num;
};

int main()
{
	my_num* p1 = new my_num;
	delete p1;

	my_num* p2 = (my_num*)malloc(sizeof(my_num));
	free(p2);

	return 0;
}

这里是引用
C语言的内存管理malloc等和C++的new/delete内存管理,单从内置类型申请空间来看,没有区别,只是C++的更方便。但是对于申请自定义类型空间的时候,就有区别了,C++的new/delete申请的时候,不只是申请空间,还会自动调用构造函数和析构函数。

4、new/delete底层原理

首先,new/delete是C++提供的申请/释放动态内存空间的操作符,其实在它的底层是调用了两个函数的,即operator new()operator delete()函数。new -> operator new()delete -> operator delete();而operator new()最终也是调用的malloc()operator delete()里调用的是free()
那么为什么在C++中要对malloc和free做一次封装了,我们知道在C语言中调用malloc()后,想要判断是否申请成功,是需要用户手写判断返回值是否为NULL,而在C++中做了封装后,为了更好适应C++中的异常机制,当通过new申请空间的时候,转去执行底层的operator new()函数,其中是做了异常检测的,当用户申请时,失败后会抛异常的,更明显。

通过上面知道,new/deletemalloc/free,在申请内置类型的时候,是一样的效果。
然而,不同的就是,malloc申请时,失败会返回NULL,而new申请失败后是抛出异常
其中new/delete是申请和释放单个数据类型的内存空间的。而new[]/delete[]是申请和释放连续的数据类型的内存空间的。

对于用户自定义类型的时候:
new原理:先调用operator new()申请内存空间;然后在申请的空间上调用构造函数,完成对象的构造。
delete原理:先是对申请的空间执行析构函数,完成空间中资源的清理,然后调用operator delete()函数释放对象的空间。

对于
new[]原理:即调用多个operator new()申请内存空间;然后在申请的空间上调用多次构造函数,完成对象的构造。
delete[]原理:即是对申请的空间执行多次析构函数,完成空间中资源的清理,然后多次调用operator delete()函数释放对象的空间。

5、malloc/free和new/delete的区别:

malloc是怎样分配空间的?分配的是物理空间还是虚拟空间?申请后是否立即得到物理内存?free释放的时候是怎样知道释放多大的空间?
首先malloc\free是C语言中的库函数,而new\delete是C++中的操作符

共同点:
它们的共同作用就是:申请内存和释放内存。

malloc和new 差异:
(1)从使用上的差异看:

malloc中是需要手动计算申请空间的大小,new是不需要手动计算申请空间的大小的

(2)从返回值的差异看:

malloc是返回申请成功的内存空间的起始地址,即返回的是不带类型的指针,即void*,使用时需要强转。int* cap=(int*)malloc(sizeof(int));
new的返回值是一个对象类型的指针,即带类型的指针。

(3)从申请失败的情况看:

new申请失败后,会抛出异常的
malloc申请失败返回的是NULL,需要用户自动判断。

(4)从分配的区域看:

malloc实际是分配的虚拟内存,通常是在堆上开辟的,若申请空间超过128kb,可能就会分配文件映射区,即mmap进行映射。小于128k是通过break系统调用的,首先走的是从内存池的申请的(申请空间小的话),若内存池没有了,就通过break系统调用从堆上申请空间。
new则是通过自由存储区分配的(free store)可能在堆上,也可能是自定义的存储区,在进行new的时候,首先调用operator new(),申请足够的空间,之后再调用构造函数,进行初始化成员变量。

(5)是否初始化:

new申请空间后,会调用构造函数初始化成员变量
malloc申请空间后,只是一个虚拟内存空间,还没有初始化,不会立即映射到物理内存的,而是用户在初始化空间的时候,才会通过页表去建立映射关系,从而找到物理内存的。

free和delete 差异:

(1)delete释放的时候是需要对象类型的指针的。而free只需要void* 的指针。那是怎样知道释放多大的空间?这个malloc原理有关,在分配空间的时候,会多申请16Byte的空间,在释放的时候会根据16Byte里的内容,里面有这个内存空间的长度信息
(2)delete会先调用析构函数,对占用的资源进行释放,然后调用operator delete(),最后释放内存空间

6、内存泄漏是什么?

  1. 内存泄露:可以形象比喻为脱缰的野马亦或没拴住的野狗,随时随地都可能有不知的危害。书面解释即,用户在使用一段内存空间的时候,由于疏忽或误操作导致程序未能释放已经不再使用的内存空间的情况,没有权利再去控制该内存空间,但是又没有还给系统,从而造成内存资源浪费。
  2. 内存泄露的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

7、如何避免内存泄漏?

解决方案分为两种:
1、事前预防型。如申请和释放匹配着使用、RAII思想(对象生命周期的管理)、智能指针等。
2、事后查错型。如泄漏检测工具。


网站公告

今日签到

点亮在社区的每一天
去签到