文章目录
- C/C++内存管理(含C++中new和delete的使用)
- 1、C/C++内存分布
- 2、C语言中动态内存管理方式:malloc/calloc/realloc/free
- 3、C++动态内存管理
- 3.1、new/delete操作内置类型
- 3.2、new/delete操作自定义类型
- 4、operator new与operator delete函数
- 5、new和delete的实现原理
- 5.1、内置类型
- 5.2、自定义类型
- 6、定位new表达式(placement-new)
C/C++内存管理(含C++中new和delete的使用)
1、C/C++内存分布
我们先来看下面的一段代码和相关问题。
int globalVar = 1; static int staticGlobalVar = 1; int main() { 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); return 0; } //1. 选择题: // 选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区) // globalVar在哪里?____ staticGlobalVar在哪里?____ // staticVar在哪里?____ localVar在哪里?____ // num1 在哪里?____ // // char2在哪里?____ *char2在哪里?___ // pChar3在哪里?____ *pChar3在哪里?____ // ptr1在哪里?____ *ptr1在哪里?____ //2. 填空题: // sizeof(num1) = ____; //sizeof(char2) = ____; strlen(char2) = ____; // sizeof(pChar3) = ____; strlen(pChar3) = ____; // sizeof(ptr1) = ____; //3. sizeof 和 strlen 区别? //
- 说明:
- 栈 又叫堆栈–非静态局部变量/函数参数/返回值等等,栈是向下增长的。
- 内存映射段 是高效的
I/O
映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。(Linux
课程如果没学到这块,现在只需要了解一下)- 堆 用于程序运行时动态内存分配,堆是可以上增长的。
- 数据段 – 存储全局数据和静态数据。
- 代码段 – 可执行的代码/只读常量。
所以1.选择题有答案了。
那么2.填空题答案是:之前学过不讲了哈哈。
其中3.
sizeof
和strlen
区别?答:
strlen
遇到\0
结束,strlen
算字符串长度,sizeof
算变量大小。
2、C语言中动态内存管理方式:malloc/calloc/realloc/free
malloc
、calloc
和realloc
是在C语言中用于动态内存分配的三个函数,它们有一些区别,主要体现在它们的功能和用法上。
malloc(Memory Allocation,内存分配):
malloc
是 “memory allocation” 的缩写,用于分配指定大小的内存块。- 它只分配内存,不对内存进行初始化,所以分配的内存中可能包含任意值。
void* malloc(size_t size);
calloc(Contiguous Allocation,连续分配):
calloc
也是用于分配内存的函数,但与malloc
不同的是,calloc
分配的内存块会被初始化为零。calloc
接受两个参数,分别是所需的元素个数和每个元素的大小。void* calloc(size_t num_elements, size_t element_size);
realloc(Re-allocation,重新分配):
realloc
用于重新分配已分配内存的大小,可以用于扩大或缩小内存块的大小。- 如果原始内存块的地址不为空,
realloc
会尝试在原始地址上修改内存块的大小(如果新内存大小大于这块原始空间,则还是需要重新开辟内存),如果原始地址为空,则行为类似于malloc
。void* realloc(void* ptr, size_t new_size);
总结:
malloc
只分配指定大小的内存块,不进行初始化。calloc
分配指定数量和大小的内存块,并将内存块的所有位初始化为零。realloc
重新分配内存块的大小,可以用于扩大或缩小已分配内存的大小。
int main() { int *p1 = (int *) malloc(sizeof(int)); free(p1); // 1.malloc/calloc/realloc的区别是什么? int *p2 = (int *) calloc(4, sizeof(int)); int *p3 = (int *) realloc(p2, sizeof(int) * 10); // 这里需要free(p2)吗? --- 看情况 free(p3); return 0; }
3、C++动态内存管理
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力(比如给对象初始化),而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
3.1、new/delete操作内置类型
int main() { // 动态申请一个int类型的空间 int *ptr4 = new int; // 动态申请一个int类型的空间并初始化为10 int *ptr5 = new int(10); cout << *ptr5 << endl; // 动态申请10个int类型的空间 int *ptr6 = new int[3]; // 动态申请10个int类型的空间并初始化前3个 int *ptr7 = new int[3]{1, 2, 3}; cout << ptr7[0] << ptr7[1] << ptr7[2] << ptr7[3]; delete ptr4; delete ptr5; delete[] ptr6; delete[] ptr7; }
- 初始化格式:
- 单个元素空间:
new 类型 (初始化值)
- 连续空间:
new 类型 [元素个数] {从前往后元素初始化值,其余元素初始化为0}
- 注意:申请和释放单个元素的空间,使用
new
和delete
操作符,申请和释放连续的空间,使用new[]
和delete[]
。注意:匹配起来使用。
3.2、new/delete操作自定义类型
class Date { public: Date() : _year(1), _month(1), _day(1) { cout << "Date()" << endl; } Date(int year, int month, int day) : _year(1), _month(1), _day(1) { _year = year; _month = month; _day = day; cout << "Date()" << endl; } ~Date() { cout << "~Date()" << endl; } private: int _year; int _month; int _day; }; int main() { Date *ptr1 = new Date(); Date *ptr2 = new Date(2, 2, 2); Date *ptr3 = new Date[10]{{1, 2, 2}}; free(ptr1); delete ptr2; delete[] ptr3; return 0; }
- 初始化格式:
- 单个元素空间:
new 类型 (初始化值)
- 连续空间:
new 类型 [元素个数] {从前往后元素初始化值使用{}代表每一个元素的值,其余元素初始化为0}
new/delete
和malloc/free
最大区别是new/delete
对于【自定义类型】除了开空间还会调用构造函数和析构函数。
4、operator new与operator delete函数
new
和delete
是用户进行动态内存申请和释放的操作符,operator new
和operator delete
是系统提供的全局函数,new
在底层调用operator new
全局函数来申请空间,delete
在底层通过operator delete
全局函数来释放空间。
operator new
:该函数实际通过malloc
来申请空间,当malloc
申请空间成功时直接返回;申请空间失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。operator delete
最终是通过free
来释放空间的。class Date { public: Date() : _year(1), _month(1), _day(1) { cout << "Date()" << endl; } Date(int year, int month, int day) : _year(1), _month(1), _day(1) { _year = year; _month = month; _day = day; cout << "Date()" << endl; } ~Date() { cout << "~Date()" << endl; } private: int _year; int _month; int _day; }; int main() { //operator new -- 不调用构造函数 和 malloc 基本一样 Date *ptr6 = (Date *) operator new(sizeof(Date)); delete new(ptr6) Date; ptr6 = nullptr; return 0; }
5、new和delete的实现原理
5.1、内置类型
如果申请的是内置类型的空间,
new
和malloc
,delete
和free
基本类似,不同的地方是:new
/delete
申请和释放的是单个元素的空间,new[]
和delete[]
申请的是连续空间,而且new
在申请空间失败时会抛异常,malloc
会返回NULL
。
5.2、自定义类型
new
的原理
- 调用
operator new
函数申请空间- 在申请的空间上执行构造函数,完成对象的构造
delete
的原理
- 在空间上执行析构函数,完成对象中资源的清理工作
- 调用
operator delete
函数释放对象的空间
new T[N]
的原理
调用
operator new[]
函数,在operator new[]
中实际调用operator new
函数完成N
个对象空间的申请在申请的空间上执行
N
次构造函数
delete[]
的原理
在释放的对象空间上执行
N
次析构函数,完成N个对象中资源的清理调用
operator delete[]
释放空间,实际在operator delete[]
中调用operator delete
来释放空间这里我们需要注意一个现象:对于内置类型在
new T[N]
时候,往往开辟的内存空间会大于N*sizeof(T)
,可能会多出4
个字节。原本这里
ptr2
开辟的空间size应该是12*10 = 120
,但是这里显示124
,其中4
个字节是用来存储开辟连续Date
对象的个数。class Date { public: Date() : _year(1), _month(1), _day(1) { cout << "Date()" << endl; } Date(int year, int month, int day) : _year(1), _month(1), _day(1) { _year = year; _month = month; _day = day; cout << "Date()" << endl; } ~Date() { cout << "~Date()" << endl; } private: int _year; int _month; int _day; }; int main() { Date* ptr1 = new Date; delete ptr1; Date* ptr2 = new Date[10]; delete[] ptr2; return 0; }
但是如果是内置类型
new T[N]
,则不需要另外的空间存个数。原本这里
ptr3
开辟的空间size
应该是4*10 = 40
,并且这里显示40
,即没有开辟空间存个数。class Date { public: Date() : _year(1), _month(1), _day(1) { cout << "Date()" << endl; } Date(int year, int month, int day) : _year(1), _month(1), _day(1) { _year = year; _month = month; _day = day; cout << "Date()" << endl; } ~Date() { cout << "~Date()" << endl; } private: int _year; int _month; int _day; }; int main() { Date* ptr1 = new Date; delete ptr1; Date* ptr2 = new Date[10]; delete[] ptr2; int* ptr3 = new int[10]; delete[] ptr3; return 0; }
原因:这多出来的
4
个字节是用来记录开辟T
大小连续空间的个数,以便于delete []
进行析构和释放空间。这里如果不写析构函数(默认成员变量没有开辟空间,如果开辟了空间,必须调用析构函数释放空间,不然会内存泄露)的话就不报错,因为成员变量都是内置类型没有开空间,不需要调用析构函数,也就不需要在前面添加
4
字节存个数,即自定义类型new T[N]
可以直接delete
,内置类型也一样。class Date { public: Date() : _year(1), _month(1), _day(1) { cout << "Date()" << endl; } Date(int year, int month, int day) : _year(1), _month(1), _day(1) { // _a = new int[10]; _year = year; _month = month; _day = day; cout << "Date()" << endl; } // ~Date() { // cout << "~Date()" << endl; // } private: // int* _a; int _year; int _month; int _day; }; int main() { Date *ptr1 = new Date; delete ptr1; Date *ptr2 = new Date[10]; // 这里如果不写析构函数(默认成员变量没有开辟空间,如果开辟了空间,必须调用析构函数释放空间,不然会内存泄露)的话就不报错 // 因为成员变量都是内置类型没有开空间,不需要调用析构函数,也就不需要在前面添加4字节存个数 delete ptr2; return 0; }
原本这里
ptr2
开辟的空间size应该是12*10 = 120
,并且这里显示120
,即没有开辟空间存对象个数。
6、定位new表达式(placement-new)
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
- 使用格式:
new (place_address) type
或者new (place_address) type(initializer-list)
place_address
必须是一个指针,initializer-list
是类型的初始化列表- 使用场景:
- 定位
new
表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new
的定义表达式进行显示调构造函数进行初始化。class Date { public: Date() : _year(1), _month(1), _day(1) { cout << "Date()" << endl; } Date(int year, int month, int day) : _year(1), _month(1), _day(1) { _year = year; _month = month; _day = day; cout << "Date()" << endl; } ~Date() { cout << "~Date()" << endl; } private: int _year; int _month; int _day; }; int main() { Date *p = (Date *) operator new(sizeof(Date)); // 不能显式调用构造函数 // p->Date(); // 定位new可以显式调用构造函数 new(p)Date(1, 1, 1); p->~Date(); return 0; }
OKOK,C/C++内存管理就到这里。如果你对Linux和C++也感兴趣的话,可以看看我的主页哦。下面是我的github主页,里面记录了我的学习代码和leetcode的一些题的题解,有兴趣的可以看看。
Xpccccc的github主页