深入理解C/C++内存管理
- 引言
- 1、内存布局基础
- 2、动态内存管理
- 2.1 C风格内存管理
- 2.2 C++运算符`new`/`delete`
- 2.3 匹配使用问题
- 3、区别
引言
在系统级编程中,C/C++因其直接操作内存的能力而备受青睐,但这也意味着开发者需要承担内存管理的责任。内存泄露、段错误(Segmentation Fault)和悬空指针等问题往往源于对内存管理机制理解不足。本文将深入探讨C/C++内存管理的关键机制、常见陷阱等。
1、内存布局基础
C/C++程序运行时内存分为以下区域:
- 栈(Stack)
- 存储局部变量、函数参数。
- 自动管理,采用后进先出(LIFO)模式,分配/回收由编译器隐式完成。
- 容量有限,过度使用(例如无限递归)导致栈溢出(Stack Overflow)。
- 堆(Heap)
- 动态分配,需手动管理(
malloc
/free
,new
/delete
)。 - 容量较大但分配效率低于栈。
- 内存泄露高发区。
- 动态分配,需手动管理(
- 静态区
- 存储全局变量、静态变量(
static
)。 - 生命周期持续至程序结束。
- 存储全局变量、静态变量(
- 代码区
- 存储可执行指令。
2、动态内存管理
2.1 C风格内存管理
#include <stdio.h>
#include <stdlib.h>int main(void) {int *array = (int *)malloc(sizeof(int) * 10);if (array == NULL) {perror("malloc");}free(array);return 0;
}
malloc
/calloc
/realloc
分配内存失败会返回NULL
。- 陷阱:
- 忘记
free
导致内存泄露。 - 重复释放(Double Free)。
- 访问已释放内存(悬空指针)。
- 需要手动计算分配空间大小,结构体类型容易疏忽导致分配内存大小错误。
- 忘记
2.2 C++运算符new
/delete
#include <iostream>int main() {int* a = new int(10); // 分配空间并初始化std::cout << *a << std::endl;delete a;return 0;
}
#include <iostream>int main() {int* a = new int[10]; // 分配40字节大小空间for (int i = 0; i < 10; i++) {a[i] = i;}for (int i = 0; i < 10; i++) {std::cout << a[i] << std::endl;}delete[] a;return 0;
}
- 优势:
- 自动计算大小,类型安全,无需显式类型转换。
- 调用构造函数/析构函数。
#include <iostream>class Stack {
public:Stack(int n = 5) {data_ = new int[n];top_ = 0;capacity_ = n;}~Stack() {delete[] data_;top_ = capacity_ = 0;}
private:int* data_;int top_;int capacity_;
};int main() {Stack* p_stack = new Stack(10);delete p_stack;return 0;
}
#include <iostream>class Stack {
public:Stack(int n = 5) {data_ = new int[n];top_ = 0;capacity_ = n;}~Stack() {delete[] data_;top_ = capacity_ = 0;}
private:int* data_;int top_;int capacity_;
};int main() {Stack* p_stack = (Stack*)malloc(sizeof(Stack));free(p_stack);return 0;
}
- 因此,
new
/delete
非常适用于自定义类型动态开辟空间,可以完成类的初始化和释放类内动态开辟资源。
2.3 匹配使用问题
malloc
/free
成对出现不必多说,new
申请数组时,需要使用delete[]
来搭配使用,如果单纯使用delete
会发生什么现象呢?
class A {
public:A(int a = 0): a_(a) {}
private:int a_;
};int main() {A* pa = new A[10];delete pa;return 0;
}
- 以上代码会正常运行,这样看来使用
delete
来释放指向连续空间的指针也是可以的,但真的是这样吗? - 让我们将代码变换一下:
class A {
public:A(int a = 0): a_(a) {}~A() {}
private:int a_;
};int main() {A* pa = new A[10];delete pa;return 0;
}
- 我们发现只是显式编写了析构函数,程序就报错了。
- 当我们显示定义了析构函数(即使它是空的),编译器对对象销毁的要求更加严格,错误地用
delete
释放new[]
分配的内存会被明确指出。 - 因此在使用上,一定要匹配使用。
3、区别
malloc
分配空间不会初始化,new
可以初始化。- 在申请自定义类型空间时,
malloc
/free
只申请/释放空间,new
/delete
会调用自定义类型的构造/析构函数,完成对象的初始化和释放资源。 malloc
事情空间时需要手动计算空间大小,new
后面只需要接类型即可,申请连续空间也只需要使用[n]
指明申请数据量即可。malloc
申请空间失败返回NULL
,new
申请空间失败会抛异常。malloc
的返回值是void*
,因此需要强制类型转换;new
不需要。malloc
/free
是函数,new
/delete
是操作符。