C/C++的内存分布
在c语言中,我们动态申请内存是通过malloc、calloc、realloc,释放内存是通过free来实现的,而在c++中,我们又可以通过new和delete来申请和释放内存。
那么就有一个问题,对于内存来说,或者对于数据来说,它们是如何存储的,在内存中的分布又是什么样子的呢?
实际上,我们可以将内存指针指向的进程虚拟地址空间分为以下几个部分:
栈区、堆区、共享区、数据段、代码段
而它们不同的区域是负责存放不同类型的数据的。
栈区 | 堆区 | 全局/静态存储区 | 代码段(常量存储区) | 内存映射段 |
---|---|---|---|---|
局部变量、函数参数等,无需手动释放 | malloc或者new开辟的空间,需要手动释放 | 全局变量、静态变量 | 代码、字符常量 | 用于装载一个共享的动态内存库 |
函数作用域内创建,作用域结束后自动销毁,存储空间连续,默认8M(ulimit -s查看 ) | 空间不连续,大小不受限,频繁创建容易产生内存碎片 | 内部分为BSS段(存放未初始化的全局变量)和数据段(存放已初始化的全局变量) | 不允许被修改 | 可用于进程间通信 |
用代码演示就是这样
C和C++的内存管理方式
c语言中,使用malloc、calloc、realloc和free进行内存管理,但是需要我们自己判断是否创建成功,还需要进行类型转换。
c++更加便捷,使用new和delete进行内存管理,就无需进行类型转换和判断是否创建成功,因为创建失败会抛异常。本质上,new和delete实现底层还是使用malloc和free。
new和delete函数
对于new和delete函数,它们既可以创建内置类型空间,也可以创建自定义类型。
核心部分是,当我们使用new和delete进行自定义类型的创建的销毁时,会调用构造函数和析构函数,这意味着创建的空间中自定义类型对象会被初始化。
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
// new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数
A* p1 = (A*)malloc(sizeof(A));
A* p2 = new A(1);
free(p1);
delete p2;
// 内置类型是几乎是一样的
int* p3 = (int*)malloc(sizeof(int)); // C
int* p4 = new int;
free(p3);
delete p4;
A* p5 = (A*)malloc(sizeof(A)*10);
A* p6 = new A[10];
free(p5);
delete[] p6;
return 0;
}
本质上,new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。
意味着使用new创建空间时,编译器实际上会调用void* operator new(size_t size)
函数申请空间,至于创建多少个字节,编译器会根据传递的类型自动推算字节数。
new和delete的实现原理
new的原理(分为两大步)
一、1.使用malloc申请空间
一、2.循环检测空间是否申请成功(若成功,循环结束,直接返回;若失败—>空间不足,尝试内存空间不足的应对措施)
在(2)循环中,若存在内存不足的应对措施,则继续循环申请,若不存在措施,则bad_alloc抛异常
二、在申请成功的空间上指向构造方法—>只针对申请的类型是自定义类型
delete的原理(两大步)
一、调用析构函数清理对象中的资源—>前提是指向的自定义类型空间,并且类中提供了显式定义的析构函数
二、调用void* operator delete(void* p)
释放堆空间---->释放的是对象自身的空间(对象中的资源在第一步时已经通过析构函数进行了清理)
new[]的原理和new原理类似,只是由于new[]是创建一个连续的类型空间,需要进行循环申请。
一、申请空间:计算所需空间的大小
二、调用N次构造函数,从前往后逐个构造每个对象
delete[]原理和delete原理类似
一、调用N次析构函数,从后往前清理每个对象中的资源
二、释放对象自身的资源
常见的面试题
1.malloc(10*sizeof(int))
malloc一定会向内存申请40个字节吗?
并不是:而是会向内存申请76字节的空间。多出来的36字节中,前32字节在申请的40字节之前,负责存放管理当前申请的空间,例如记录了当前空间的大小,后4个字节在申请的40个字节之后,为了防止内存越界。
2.malloc的实现原理(后续博客会专门讲述)
3.malloc/calloc/realloc的区别
相同点 | 不同点 |
---|---|
都是c标准库提供的库函数,需要stdlib.h头文件 | malloc需要的参数是申请空间总字节数 |
使用完成后,需要free释放空间,否则内存泄漏 | calloc第一个参数是空间个数,第二个参数是每个空间的字节数,并且会将每个字节初始化为0 |
返回值类型都是void*,需要进行强转 | realloc第一个参数是原地址,第二个参数是调整后的字节数。特殊的是,如果需要扩大空间,则会有两种情况 |
若申请空间成功,返回首地址,失败返回NULL,因此需要判空 | realloc扩大空间的两种情况(1.扩大的空间可以在原有空间上扩大 2.后续空间不足,不能在原有空间上扩大)。对于第二种情况:1.申请新空间2.将原有空间中的数据拷贝到新空间3.释放旧空间4.返回新空间的地址 |
4.malloc/free和new/delete的区别
共同点:都是从堆上申请空间,并且需要手动释放
不同点:
1.malloc/free是函数,而new/delete是操作符,因此不需要包含头文件
2.malloc创建的空间不会进行初始化,而new创建的空间会,并且创建的自定义类型会调用构造函数
3.malloc申请空间要传递空间的字节数,并且强转,new只需传递类型,也无需强转
4.malloc创建空间失败时,返回NULL,需要判空,而new不需要,并且创建失败会抛异常
5.malloc和free只会开辟和释放内存,不会调用构造和析构函数。而new/delete会调用构造和析构函数
5.内存泄漏
5.1什么是内存泄漏,内存泄漏有什么危害
内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。
内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
5.2内存泄漏的分类
分为堆内存泄漏和系统资源泄漏
堆内存泄漏:malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉,否则就是堆内存泄漏
系统资源泄漏:指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
5.3如何检测内存泄漏
在VS中,使用的_CrtDumpMemoryLeaks() 函数进行简单检测,也可以使用其他第三方工具进行检测VLD
在Linux中,可以使用以下几种工具linux内存泄漏检测工具
5.4如何避免内存泄漏
1、事前预防型。如智能指针等。(良好的编码规范)
2、事后查错型。如泄漏检测工具。