C++内存管理(超详细解析)
目录
十、operator new 与 operator delete
一、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);
}
二、程序内存区域划分
C/C++ 程序运行时,内存通常分为以下几个区域:
| 区域 | 作用 |
|---|---|
| 栈(Stack) | 存放局部变量、函数参数等 |
| 堆(Heap) | 动态申请的内存 |
| 数据段/静态区 | 全局变量、静态变量 |
| 代码段/常量区 | 程序代码、字符串常量 |
三、代码变量内存分析
1. globalVar 在哪里?
int globalVar = 1;
属于数据段(静态区)
因为它是全局变量。
2. staticGlobalVar 在哪里?
static int staticGlobalVar = 1;
属于数据段(静态区)
static 修饰的全局变量仍然在静态区。
3. staticVar 在哪里?
static int staticVar = 1;
属于数据段(静态区)
静态局部变量生命周期是整个程序运行期间。
4. localVar 在哪里?
int localVar = 1;
属于栈
局部变量存放在栈区。
5. num1 在哪里?
int num1[10];
属于栈
数组本身是局部变量。
6. char2 在哪里?
char char2[] = "abcd";
属于栈
因为这是字符数组,会把字符串拷贝到数组中。
7. *char2 在哪里?
char2[0]
属于栈,这里错误率很高,很多同学认为*char就是“abcd”,在常量区,但是这里是把“abcd”拷贝在了这个char数组里,数组内容在栈上。我们知道,char2是数组名,而数组名又代表数组首元素的地址,*char2就是char[0],即a
8. pChar3 在哪里?
const char* pChar3 = "abcd";
属于栈
pChar3 本身是局部指针变量。
9. *pChar3 在哪里?
"abcd"
这个就属于代码段(常量区)了,因为指针指向常量区
字符串常量存储在常量区。
10. ptr1 在哪里?
int* ptr1 = (int*)malloc(sizeof(int) * 4);
ptr1 本身是在栈上,因为它是局部变量。
11. *ptr1 在哪里?
malloc申请的空间
属于堆,ptr指向malloc 从堆区申请的空间。
四、C语言动态内存管理
C语言主要通过以下函数进行动态内存管理:
malloc
calloc
realloc
free
1. malloc
功能
动态申请指定字节大小的空间。
用法
int* p = (int*)malloc(sizeof(int) * 10);
特点
-
申请的空间不初始化
-
返回 void*
-
失败返回 NULL
2. calloc
功能
申请空间并初始化为0。
用法
int* p = (int*)calloc(10, sizeof(int));
特点
-
自动初始化为0
-
参数是:
-
元素个数
-
单个元素大小
-
3. realloc
功能
扩容或缩容动态空间。
用法
p = (int*)realloc(p, sizeof(int) * 20);
特点
-
可能原地扩容
-
也可能开辟新空间后拷贝数据
-
返回新地址
void Test ()
{
// 1.malloc/calloc/realloc的区别是什么?
int* p2 = (int*)calloc(4, sizeof (int));
int* p3 = (int*)realloc(p2, sizeof(int)*10);
// 这里需要free(p2)吗?
free(p3 );
}
如果是原地扩容,p2==p3,释放p2就是释放p3
如果是异地扩容,编译器会自己释放p2
所以我们不需要自己手动释放p2
4. free
功能
释放动态申请的空间。
用法
free(p);
五、malloc/calloc/realloc 区别
| 函数 | 特点 |
|---|---|
| malloc | 申请空间但不初始化 |
| calloc | 申请空间并初始化为0 |
| realloc | 调整空间大小 |
六、C++ 动态内存管理
C++ 提供:
new
delete
用于动态内存管理。
1. new/delete 操作内置类型
int* p1 = new int;
int* p2 = new int(10);
int* p3 = new int[3];
delete p1;
delete p2;
delete[] p3;
注意:
-
单个对象:
new/delete
-
数组对象:
new[]/delete[]
必须配套使用。
int main()
{
int* p1 = new int;
int* p2 = new int[10];
delete p1;
delete[] p2;
// +ʼ
int* p3 = new int(0);
int* p4 = new int[10]{ 0 };
int* p5 = new int[10]{1,2,3,4,5};
delete p3;
delete[] p4;
delete[] p5;
return 0;
}
p1为单个对象
p2为多个对象
p3表示对单个对象进行初始化
p4表示对10个对象全部初始化为0
p5表示对10个对象中前5个初始化为1,2,3,4,5,后5个初始化为0

七、new/delete 与 malloc/free 区别
| 对比项 | malloc/free | new/delete |
|---|---|---|
| 本质 | 函数 | 操作符 |
| 初始化 | 不会 | 可以 |
| 返回值 | void* | 对应类型指针 |
| 失败处理 | 返回NULL | 抛异常 |
| 自定义类型 | 不调用构造析构 | 调用构造析构 |
八、自定义类型与构造析构
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
malloc/free
A* p1 = (A*)malloc(sizeof(A));
free(p1);
特点:
-
只开空间
-
不调用构造函数
-
不调用析构函数
new/delete
A* p2 = new A(1);
delete p2;
特点:
-
会调用构造函数
-
会调用析构函数
int main()
{
A* p1 = new A;
A* p2 = new A(1);
delete p1;
delete p2;
}
这里对p1,p2会调用构造和析构

那么为什么我们要用new,看以下代码
struct ListNode
{
int val;
ListNode* next;
ListNode(int x)
:val(x)
, next(nullptr)
{}
};
int main()
{
ListNode* n1 = new ListNode(1);
ListNode* n2 = new ListNode(1);
ListNode* n3 = new ListNode(1);
ListNode* n4 = new ListNode(1);
n1->next = n2;
n2->next = n3;
n3->next = n4;
return 0;
}
在c语言中,我们要创建一个节点,还记得我们是怎么操作的吗?申请一个节点BuyNode()
SLTNode* SLTBuyNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc fail!");
exit(1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
这里申请空间后还要自己手动赋值,非常的麻烦
而用new,我们既不用调用malloc函数,而且也不用手动赋值,因为new会帮我们调用构造函数,帮助我们赋值,非常的方便,也就是说我们可以用一句代码
ListNode* n1 = new ListNode(1);
直接代替掉上面一整个函数
然后下面是构造的一些方式
A(int a1 = 0, int a2 = 0)
:_a1(a1)
, _a2(a2)
{
cout << "A(int a1 = 0, int a2 = 0)" << endl;
}
int main()
{
A* p1 = new A(1);
A* p2 = new A(2,2);
A aa1(1, 1);
A aa2(2, 2);
A aa3(3, 3);
A* p3 = new A[3]{aa1, aa2, aa3};
A* p4 = new A[3]{ A(1,1), A(2,2), A(3,3)};
//A aa1 = { 1, 1 };
A* p5 = new A[3]{ {1,1}, {2,2}, {3,3} };
return 0;
}
p1,p2就是正常的构造
p3就是有名对象构造,这里3个对象会调用3次(先构造,再拷贝构造,但是现在的编译器有的会优化,因为编译器会觉得先构造再拷贝构造太麻烦了,直接优化成构造)
p4为匿名对象构造,这里3个对象会调用3次(先构造,再拷贝构造,但编译器也会优化成构造)
p5为隐式类型转换,c++既支持单参数隐式类型转换,也支持多参数的,用花括号括起来
{1,1}会构造成一个A的临时对象,然后用这个临时对象拷贝构造(然后编译器可以优化),本质上跟p4是一样的
九、new申请空间失败
我们知道c语言中malloc函数申请空间失败会返回NULL指针,而new申请空间失败会抛异常
正常来说我们申请空间基本上不会失败,因为堆空间有4G左右内存,我们平常申请的三瓜两枣完全是够用的,如果我们申请了很大空间,new可能会抛异常
int main()
{
try
{
// throw try/catch
void* p1 = new char[1024 * 1024 * 1024];
cout << p1 << endl;
void* p2 = new char[1024 * 1024 * 1024];
cout << p2 << endl;
void* p3 = new char[1024 * 1024 * 1024];
cout << p3 << endl;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
这里我们申请了3G内存,编译器会抛异常
![]()
我们也可以看看new能申请多少空间
void func()
{
// throw try/catch
int n = 1;
while (1)
{
void* p1 = new char[1024 * 1024];
cout << p1 << "->"<< n<<endl;
++n;
}
}
int main()
{
try
{
func();
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}

可以看见申请了1899MB空间后就不能再申请了
十、operator new 与 operator delete
new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是
系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过
operator delete全局函数来释放空间。
实际上:
new
底层调用:
operator new
汇编指令

new会调用operator new,然后调用构造
而:
delete
底层调用:
operator delete

先调用析构,在调用operator delete
更多推荐

所有评论(0)