1.C/C++内存分布
-
代码区(Text Segment):也称为文本区,用于存放程序的机器代码,包括程序执行的指令。这部分内存是只读的,以防止程序代码在执行过程中被意外修改。
-
数据区(Data Segment):用于存放程序中定义的静态变量和全局变量,这些变量在程序启动时初始化。数据区又可以分为两部分:
- 已初始化数据区(Initialized Data Segment):存放有初始值的静态变量和全局变量。
- 未初始化数据区(BSS Segment):存放未初始化的静态变量和全局变量,这些变量在程序启动时会被自动初始化为0或空。
-
堆(Heap):堆是程序运行时动态分配内存的区域,使用malloc、calloc、realloc等函数在堆上分配的内存需要程序员手动释放。堆的大小在程序运行过程中可以动态变化。
-
栈(Stack):栈是用于存放函数调用时的局部变量、函数参数、返回地址以及一些寄存器的值。栈是一种后进先出(LIFO)的数据结构,每次函数调用都会在栈上创建一个新的栈帧(Stack Frame),函数返回时,该栈帧会被销毁。栈的大小通常在程序启动时设定,超出栈空间会导致栈溢出错误。
-
常量区:用于存放程序中的字符串常量和其他常量,这部分内存也是只读的。
-
附加段(TLS,Thread Local Storage):用于存放线程局部存储的数据,这些数据对于每个线程来说是私有的。
2.C语言中动态内存管理
在C语言中,动态内存管理是通过一组标准库函数来实现的,这些函数主要包括以下几个:
-
malloc()
:分配指定大小的内存块。它返回一个指向void类型的指针,因此需要强制类型转换到所需类型。如果分配失败,返回NULL。 -
calloc()
:与malloc()
类似,但它会清除分配的内存,将其初始化为0。它接受两个参数,第一个是元素数量,第二个是每个元素的大小。 -
realloc()
:用于调整之前分配的内存块的大小。它可以扩大或缩小内存块,并返回一个新的指针。如果新指针与旧指针不同,原始数据可能会被复制到新的位置。 -
free()
:释放之前分配的内存。这是非常重要的,因为如果不释放不再使用的内存,会导致内存泄漏。
动态内存管理的使用场景包括:
- 当不确定需要多少内存时,例如,当处理用户输入或数据结构的大小可能会变化时。
- 当需要大量内存时,可能不希望在使用栈上分配,因为栈空间有限。
- 当需要的数据结构在编译时大小未知,或者需要在运行时动态创建和销毁时。
正确使用动态内存管理是C语言编程中的一个重要方面,需要遵循一些最佳实践来避免内存泄漏和野指针等问题。例如,一旦使用完毕,就应该释放分配的内存;在重新分配内存后,应该更新所有指向原始内存块的指针;避免对已经释放的内存进行访问或释放等。
3.C++中动态内存管理
针对内置类型:
(1)C++兼容c语言,内置类型的动态申请,用法简化了,功能保持一致
int* p=(int*)malloc(sizeof(int)); //自己算大小,且要强制转换 C
int* p1=new int; //别人帮你自动计算大小,不需要强转 C++
int* p2=(int*)malloc(sizeof(int)*10);
int* p3=new int[10];
free(p);
free(p2);
delete p1;//和new配套使用
delete[] p3;//和new配套使用
(2)开辟空间以后,为了用起来更方便,c++引入了新语法进行初始化等操作
//额外支出开空间+初始化
int* p4=new int(10);//这是初始化
//如果new一个数组那该如何初始化呢?
int* p5=new int[10]{0,1,2,3,4,5,6,7,8,9};//这是初始化
int* p5=new int[10]{};//同理还可以这样写,默认初始化为0
__________________________________________________________________________
针对自定义类型:
(1)因为malloc没有办法很好支持动态申请的自定义对象初始化
//自定义类型,开空间+调用构造函数初始化
A* p2=new A;
A* p3=new A(3);
//自定义类型,调用析构函数+释放空间
delete p2;
delete p3;
//定义多个自定义类型
A* p4=new A[10];
delete[] p4;
//定义多个自定义类型且初始化
A aa1(1);
A aa2(2);
A* p5=new A[10]{aa1,aa2};//{}这里可以初始化10个对象,没写的位置调用默认构造
delete[] p5;
//还有简便方式定义且初始化多个自定义类型
A* p6=new A[10]{A(1),A(2)};//使用匿名对象
delete[] p6;
A* p7=new a[10]{1,2};//隐式类型转换
delete[] p7;
4.oprator new 与oprator delete函数
new和delete是用户进行动态内存申请和释放的操作符,oprator new和oprator delete是系统提供的全局函数属于库函数,new在底层调用oprator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。但是他们终归最后还是用malloc和free来释放空间的。
通过上边的了解我们就会发现new 和 malloc的本质区别在于,new是内置的运算符,他会调到用底层的oprator new的库函数,而malloc本身就是库函数,并且oprator new这个函数也是基于malloc函数所写出来的。delete也是一样。
5.new 和delete的实现原理
5.1内置类型
如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的是new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回null.
5.2自定义类型
(1)new的原理:
1.在调用operator new 函数申请空间
2.在申请的空间上执行构造函数,完成对象的构造
(2)delete的原理:
1.在申请过的空间上执行析构函数,完成对象中资源的清理工作
2.调用oprator delete函数释放对象的空间
(3)new T[N]的原理:
1.调用operator new[]函数,在operator new[]中实际调用oprator new 函数完成对N个对象的申请
2.在申请空间上执行N此构造函数
(4)delete []的原理:
1.在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
2.调用oprator delete [] 函数时,在oprator delete [] 函数中实际调用oprator delete函数来释放空间
值得注意:
1.malloc和free配套使用,new和delete配套使用,new T[N]; delete []配套使用,否则后果自负!!!
2.构造函数不可以直接显示调用,但是析构函数可以。简单来说就是用对应类型的指针直接指向对应的构造函数来实现调用这是不可以的,但是析构函数是可以的。如果非要调用构造函数那么只能非显示的,请看后边的内容。
6.定位new表达式(placement-new)
定位new表达式在实际冲一般是配合内存池使用。因为内存池分配出的内存没有初始化,所有如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。
A* pst=new A;
new(pst)A(4);//这个就是显示调用构造函数,pst要写在前面,后边这个数字4就是参数,构造函数自己写了几个,就填几个就可以了。如果申请的内存是从内存池里来的可以用这种方式