这里写目录标题
- 一、回顾C/C++内存分布
- 1. 三道基础的练习题
- 2. 内存区域划分图
- 二、C 语言中动态内存的管理方式(malloc/calloc/realloc/free)
- 1. malloc() 和 calloc() 的区别和注意事项
- 2. realloc() 的用法和注意事项
- 三、C++ 中的动态内存管理方式(new/delete)
- 1. new 和 delete 操作符的使用方法
- 2. new 和 delete 操作自定义类型
- 3. new/delete 和 malloc/free 的区别
- 4. operator new 和 operator delete 函数(了解)
- 5. new 和 delete 的实现原理
- 6. 定位 new(了解)
- 四、与内存有关的常见面试题
- 1. malloc/free 和 new/delete 的区别
- 2. 什么是内存泄漏?内存泄漏的危害
一、回顾C/C++内存分布
1. 三道基础的练习题
下面通过一段代码和内存分布相关的代码和一些题目回顾一下前面 C 语言学习的内存分布。
下面是上面题目的答案:
- A A A C C
A A A D A B - 40 5 4 4/8 4 4/8
- sizeof 计算后面变量或者类型所占内存空间的大小,单位字节;
strlen 计算从后面字符地址(char*)开始的字符个数,直到遇到第一个空字符(不包括第一个空字符)。
2. 内存区域划分图
栈中的空间基本上都是用来开辟函数栈帧的,一次性开辟一块栈帧,函数中所创建的局部变量都在其中。栈是向下增长的,因为栈从高地址开始往低地址使用。可以想象把一个木桶开口朝下,然后往桶子里塞东西,这样桶子的容量就是从高到底使用。
堆中的空间在 C 语言中通过使用函数 malloc() 申请空间和函数 free() 释放空间从而来管理。堆是向上增长的,就是往开口向上的桶子塞东西。
数据段也叫静态区,专门用来存放静态数据(static)和全全局数据。
代码段也叫常量区,一般用来存放可执行的代码和字面值常量。
二、C 语言中动态内存的管理方式(malloc/calloc/realloc/free)
C 语言中 malloc() 函数和 calloc() 函数的作用是开辟空间,realloc() 函数的作用是扩容,而 free() 函数的作用是释放空间。
1. malloc() 和 calloc() 的区别和注意事项
// malloc() 和 calloc() 的区别
int* pa = (int*)malloc(sizeof(int));
int* pb = (int*)calloc(sizeof(int), 1);
cout << "*pa = " << *pa << endl;
cout << "*pb = " << *pb << endl;
程序的运行结果如下:
从上面的测试中可以得出 malloc() 和 calloc() 有两个区别:
(1)使用方法的不同:
malloc() 函数接受一个参数,该参数表示开辟空间的大小;而 calloc() 接受两个参数,第一个参数表示开辟空间的大小,第二个参数表示开辟空间的个数。
(2)对空间内容的处理不同:malloc() 函数对空间不做处理,开辟出来的空间中的值是随机值;而 calloc() 函数把空间的每个字节初始化为 0。
注意事项:
1)当使用这两个函数申请空间时,需要对申请结果进行判断,如果返回空指针(nullptr)则表示申请失败;
2)该两个函数都返回 void* 指针,所以要对返回结果进行强制类型转换成所需的指针。
完整的使用方法如下:
// malloc()
int* pa = (int*)malloc(sizeof(int));
// 检查
if (nullptr == pa)
{
perror("malloc: ");
exit(-1);
}
// calloc()
int* pb = (int*)calloc(sizeof(int), 1);
// 检查
if (nullptr == pb)
{
perror("calloc: ");
exit(-1);
}
2. realloc() 的用法和注意事项
realloc() 函数的作用是对一个已经开辟的动态内存进行扩容。该函数的函数原型如下:
// realloc() 函数原型
void* realloc(void *ptr, size_t size)
第一个参数是需要扩容的动态内存空间的首地址,第二个参数是扩容后的总空间大小(单位字节)。扩容完毕之后返回扩容后的内存空间的首地址(void*)。
注意事项:
(1)需要对扩容返回的地址进行检查,若为 nullptr 则扩容失败;
(2)如果原空间后面的空间足够,则会在原空间的后面直接扩容,扩容后和扩容前的首地址相同;如果原空间后面的空间不够,则会在堆中找一块新的空间,把原空间的内容拷贝到新空间,销毁原空间。
(3)最好是使用一个新的指针变量接收扩容后的地址,在检查之后把该地址赋值给原空间的指针变量。
完整的使用方法如下:
可以看到本次使用 realloc() 函数进行扩容不是在原空间之后进行扩容,而是寻找了一个新的空间。
三、C++ 中的动态内存管理方式(new/delete)
虽然 C 语言中的 malloc() 和 free() 仍可以在 C++ 中继续使用,但是在某些地方上这两个函数还是无能为力。所以 C++ 提出了自己的内存管理方式——new和delete操作符。
1. new 和 delete 操作符的使用方法
new 和 delete 操作符的使用方法与 C 中的 malloc() 和 free() 还是有一些差别的。
// 申请单个空间
int* pa = new int;
// 释放多个空间
delete pa;
// 申请多个空间
int* arr = new int[10];
// 释放多个空间
delete[]pa;
// 申请单个空间并初始化
int* pc = new int(10);
delete pc;
// 申请多个空间并初始化
int* pd = new int[10]{1, 2, 3, 4};
delete[]pd;
申请单个空间时:new 类型,对应的释放:delete 指针;申请多个空间时:new 类型[个数],对应的释放,delete[]指针。切记一定要配对使用,不要混淆。
可以看到使用 new 开辟空间时,只需要传递类型和个数即可,大小和返回类型 new 操作符会自己进行计算和转化。且开辟单个空间可以使用圆括号对空间初始化;开辟多个空间时可以使用花括号对空间进行初始化。
2. new 和 delete 操作自定义类型
C++ 使用 new 和 delete 而不是用 C 的 malloc() 和 free() 最重要的一点就是对于自定义类型的处理。new 和 delete 在对自定义类型进行申请空间和释放空间时会分别调用该自定义类型的构造函数的析构函数。而 malloc() 和 free() 只是单纯的开辟空间和释放空间。
// 头文件
#include <iostream>
// using 声明
using std::endl;
using std::cout;
using std::cin;
// A 类声明
class A
{
private:
int a;
public:
A();
~A();
};
// A 类成员函数定义
A::A()
: a(0)
{
cout << "A()" << endl;
}
A::~A()
{
cout << "~A()" << endl;
}
int main()
{
// 开辟单个空间
cout << "单个空间:\n";
cout << "new:\n";
A* p1 = new A;
delete p1;
cout << "\nmalloc():\n";
A* p2 = (A*)malloc(sizeof(A));
free(p2);
// 开辟多个空间
cout << "\n多个空间\n";
cout << "new:\n";
A* arr1_A = new A[3];
delete[]arr1_A;
cout << "\nmalloc():\n";
A* arr2_A = (A*)malloc(sizeof(A) * 3);
free(arr2_A);
return 0;
}
程序的运行结果如下:
通过上面的代码及运行结果可以得出,new 和 delete 在对自定义类型创建和销毁空间时会分别调用该类的构造函数(默认的)和析构函数。而 malloc() 和 free() 只是单纯的开辟和销毁空间。
而 C++ 最重要的内容之一就是类和对象,所以 malloc() 和 free() 不适合 C++ 的发展,需要新增 new 和 delete 操作符来。
3. new/delete 和 malloc/free 的区别
(1)new 在创建空间的时候可以初始化,并且在处理自定义类型时还会调用构造函数;而 malloc 只是开辟空间,不进行任何其他操作;
(2)delete 对自定义类型进行释放空间时会调用析构函数;而 free 只释放空间,不进行其他操作;
(3)new 和 delete 在申请单个空间和多个空间上的用法不同,且需要配对使用;而 malloc 和 free 不需要;
(4)new 在创建空间的时候不需要传递空间大小,也不需要强制类型转换,只需要传递空间类型和数量。
4. operator new 和 operator delete 函数(了解)
new 和 delete 是 C++ 中在堆上申请和释放空间使用的操作符,operator new 和 operator delete 是 C++ 提供的全局函数,new 和 delete 操作符在底层分别调用 operator new 和 operator delete 全局函数来申请和释放空间。
从上面的代码可以看出,operator new 和 operator delete 实际上还是通过 malloc 申请空间和 free 释放空间,只不过为了适应 C++ 添加了一些其他的操作。
5. new 和 delete 的实现原理
在处理内置类型时,new/delete 和 malloc/free 几乎没有区别,只是 new 和 delete 在创建和释放单个对象和多个对象的使用方法稍有不同。
对于自定义类型:
(1)new 的原理:
a. 调用 operator new 函数申请空间;
b. 在申请的空间上面执行构造函数完成对象的构造。
(2)delete 的原理
a. 调用该类的析构函数完成对象的清理;
b. 调用 operator delete 函数释放空间。
(3)new T[N] 的原理
a. 调用 operator new[] 函数,在 operator new[] 函数中实际调用 operator new 函数完成 N 个对象空间的申请;
b. 在申请的空间上面执行 N 次构造函数完成 N 个对象的构造。
(4)delete[] 的原理
a. 调用 N 次析构函数完成 N 个对象的清理;
b. 调用 operator delete[] 函数,在 operator delete[] 函数中实际调用 operator delete 函数释放 N 个对象的空间。
6. 定位 new(了解)
这里了解一下即可,现阶段基本上用不上这个定位 new。
四、与内存有关的常见面试题
1. malloc/free 和 new/delete 的区别
(1)malloc/free 是函数,而 new/delete 是操作符;
(2)malloc 申请空间不能初始化,new 可以初始化;
(3)malloc 申请空间需要传递申请空间的大小,还需要对返回类型进行强制类型转换,而 new 只需要传递申请空间的类型,如果申请多个对象需要在[]中填写申请个数;
(4)malloc 申请失败返回空指针(nullptr),new 申请失败捕捉异常;
(5)new/delete 在对自定义类型申请空间和释放空间时会调用其构造函数和析构函数,而 malloc/free 不会调用。
2. 什么是内存泄漏?内存泄漏的危害
内存泄漏通常指的是在堆上申请的空间使用完了之后没有释放。内存泄漏并不是物理上的消失,而是申请空间之后这块空间的使用权限暂时归程序员,但是由于忘记了释放空间把权限归还系统,导致系统能使用的空间减少。
如果长期运行的程序出现内存泄漏,影响很大,因为每次运转一遍程序都会泄露部分内存,随着程序次数的不断运行,泄露的内存越来越大,可用的内存越来越少,导致程序越来越卡。