【C++】C&C++内存管理
目录
- 一、C/C++内存分布
- 二 、C语言中动态内存管理方式:malloc/calloc/realloc/free
- 三、 C++内存管理方式
- 3.1 new/delete操作内置类型
- 3.2 new和delete操作自定义类型
- 3.3 长度域
- 四、operator new与operator delete函数
- 五、new和delete的实现原理
- 5.1 内置类型
- 5.2 自定义类型
- 六、定位new表达式(placement-new)
- 七、常见面试题
- 7.1 malloc/free和new/delete的区别
- 7.2 内存泄漏
- 7.2.1 什么是内存泄漏,内存泄漏的危害
- 7.2.2 内存泄漏分类(了解)
- 7.2.3 如何避免内存泄漏
- 结尾
一、C/C++内存分布
我们先来看下面的一段代码和相关问题
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. 选择题: 选项: 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 区别?
解答:
1、选择题:
globalVar 是全局变量,在静态区,选C。
staticGlobalVar 是静态变量,在静态区,选C。
staticVar 是静态变量,在静态区,选C。
localVar 是局部变量,在栈上,选A。
num1 是数组,在栈上,选A。
char2 是数组,在栈上,选A。
*char2 :由于char2在栈上,那么‘*char2是数组第一个元素,也在栈上,选A。
pChar3 是局部变量,在栈上,选A。
*pChar3 : 由于pChar3是指针,且指向的对象是常量区的常量字符串,*pChar3 是字符串中的第一个元素,那么*pChar3在常量区,选D。
ptr1是局部变量,在栈上,选A。
*ptr1 : 由于ptr1是指针,指向的对象是malloc出来的,那么ptr1指向的对象就在堆上,*ptr1 就是对象中的第一个元素,那么*ptr1就在堆上,选B。
2、填空题
sizeof(num1) 计算的是num1数组的大小,等于每个元素的大小*有多少个元素,带入数据:4 * 10 = 40 字节。
sizeof(char2) 计算的是char2数组的大小,等于每个元素的大小*有多少个元素,char2被常量字符串"abcd"初始化,char2数组中除了有"abcd",数组中最后一个元素还有一个隐藏的元素'\0'带入数据:1 * 5 = 5 字节。
strlen(char2) 计算的是字符串char2的长度,扫码字符串,直到'\0'为止,'\0'之前有四个元素,那么可以得到结果为 4 。
sizeof(pChar3) 计算的是指针的大小,32位和64位的指针大小分别是 4 和 8,那么这里指针的大小就是 4 或 8 。
strlen(pChar3) 计算的是pChar3指向字符串的长度,与上面strlen(char2)的解释相同,那么可以得到结果也是 4 。
sizeof(ptr1) 计算的是指针的大小,那么结果就是 4 或 8。
3、sizeof 和 strlen 区别?
sizeof 是一个运算符,而不是函数,用于获取数据类型或变量的字节大小。sizeof 在编译时计算,不会执行运行时操作。
strlen 是一个函数,用于计算以空字符 '\0' 结尾的字符串的长度。
strlen 在运行时遍历字符串,直到找到字符串的空字符'\0'。
二 、C语言中动态内存管理方式:malloc/calloc/realloc/free
void Test() { 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); }
1.malloc/calloc/realloc的区别是什么?
malloc 用于分配指定大小的内存块,不进行初始化。
calloc 用于分配指定数量和大小的内存块,并初始化为零。
realloc 用于可以分配空间也可以更改先前分配的内存块的大小。
2.这里需要free(p2)吗?
不需要,如果realloc是原地扩容那么p2就是p3
如果realloc是异地扩容,那么p2指向的内容赋值给p3后就会被销毁,再次free会导致崩溃。
三、 C++内存管理方式
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理
3.1 new/delete操作内置类型
void Test() { // 动态申请一个int类型的空间 int* ptr4 = new int; // 动态申请一个int类型的空间并初始化为10 int* ptr5 = new int(10); // 动态申请3个int类型的空间 int* ptr6 = new int[3]; delete ptr4; delete ptr5; delete[] ptr6; }
注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[],注意:匹配起来使用。
3.2 new和delete操作自定义类型
class A { public: A(int a = 0) : _a(a) { cout cout // 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; } public: A(int a = 0) : _a(a) { cout cout A* p1 = new A[10]; // 若不将析构函数屏蔽那么下面三个析构会出现的情况为 // delete p1; // 不匹配使用,程序运行崩溃 // free(p1); // 不匹配使用,程序运行崩溃 // delete (p1 - 1); //不匹配使用,程序运行正常,但调用析构函数次数不够 // free(p1-1); //不匹配使用,程序运行正常,但未调用析构函数 delete[] p1; // 匹配使用,程序运行正常 A* p2 = new A[10]; // 将析构函数屏蔽后,编译器生成默认析构函数 // 而默认析构函数没做什么事情,编译器优化 // 不在指针指向的位置前开辟空间记录申请了几个对象 // 那么析构时指针指向的位置就是开辟空间的头,可以直接析构 // 若将析构函数屏蔽那么下面三个析构会出现的情况为 // delete p2; // 不匹配使用,程序运行,由于编译器的优化 // free(p2); // 不匹配使用,程序运行,由于编译器的优化 // delete[] p2; // 匹配使用,程序运行正常 return 0; } // try to allocate size bytes void* p; while ((p = malloc(size)) == 0) if (_callnewh(size) == 0) { // report no memory // 如果申请内存失败了,这里会抛出bad_alloc 类型异常 static const std::bad_alloc nomem; _RAISE(nomem); } return (p); } // operator delete: 该函数最终是通过free来释放空间的 void operator delete(void* pUserData) { _CrtMemBlockHeader* pHead; RTCCALLBACK(_RTC_Free_hook, (pUserData, 0)); if (pUserData == NULL) return; _mlock(_HEAP_LOCK); /* block other threads */ __TRY /* get a pointer to memory block header */ pHead = pHdr(pUserData); /* verify block type */ _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead-nBlockUse)); _free_dbg(pUserData, pHead-nBlockUse); __FINALLY _munlock(_HEAP_LOCK); /* release other threads */ __END_TRY_FINALLY return; } // free的实现 #define free(p) _free_dbg(p, _NORMAL_BLOCK) public: A(int a = 0) : _a(a) { cout cout // p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行 A* p1 = (A*)malloc(sizeof(A)); new(p1)A; // 注意:如果A类的构造函数有参数时,此处需要传参 p1-~A(); free(p1); A* p2 = (A*)operator new(sizeof(A)); new(p2)A(10); p2-~A(); operator delete(p2); return 0; } // 1.内存申请了忘记释放 int* p1 = (int*)malloc(sizeof(int)); int* p2 = new int; // 2.异常安全问题 int* p3 = new int[10]; Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放. delete[] p3; }