什么是栈(Stack)?
栈是计算机内存中用于管理局部变量和函数调用的一块区域,它遵循后进先出(LIFO, Last In First Out)的原则。我们可以将栈想象成一个叠盘子的架子,后放入的盘子总是最先被取出。
栈的特点:
- 快速的内存分配和释放:栈上的内存分配是自动的,当函数执行完毕,栈上的局部变量会自动销毁,释放内存。
- 有限的空间:栈的大小是有限的,一般为几 MB。
- 局部性强:栈中的数据只在当前函数作用域内有效,函数结束后,栈上的内存会自动释放。
栈中的变量存储示例
void function() {
int a = 10; // 局部变量 a 分配在栈上
char b = 'x'; // 局部变量 b 也分配在栈上
// 当函数结束时,a 和 b 自动被销毁
}
在上述代码中,a和b都是函数function()中的局部变量,它们在函数调用时存储在栈中。随着函数的结束,栈上的这些内存会自动释放。
什么是堆(Heap)?
堆是用于动态内存分配的内存区域,它不像栈那样由编译器自动管理,而是由程序员手动控制内存的分配和释放。当我们需要在程序运行时申请一块动态内存空间时,堆是我们要使用的区域。
堆的特点:
- 灵活的内存管理:你可以在程序运行时自由申请和释放内存。
- 需要手动释放:堆上的内存不会像栈那样自动释放,必须通过 delete 或 free 显式释放,否则会导致内存泄漏。
- 空间大:堆的空间比栈大得多,适合存储需要长生命周期和较大数据的对象。
堆中的变量存储示例
void function() {
int* p = new int(20); // p 是指向堆上分配的整数的指针
char* q = new char('x'); // q 是指向堆上分配的字符的指针
// 使用完毕后,手动释放堆内存
delete p;
delete q;
}
在这个例子中,p 和 q 是指针,它们本身存储在栈上,但它们指向的实际数据(整数和字符)是在堆上分配的。必须手动调用 delete 来释放这些堆内存,否则会出现内存泄漏。
栈与堆的区别
常见的内存错误
栈溢出
当程序分配过多的栈内存时,栈会溢出,导致程序崩溃。例如,过深的递归调用或分配超大数组都会导致栈溢出。
void recursiveFunction() {
int arr[100000]; // 超大数组可能导致栈溢出
recursiveFunction(); // 无限递归导致栈溢出
}
内存泄漏
当在堆上分配的内存没有被正确释放时,内存泄漏会发生,导致程序运行中消耗的内存越来越多。
void function() {
int* p = new int(10); // 动态分配内存
// 忘记调用 delete 释放内存,导致内存泄漏
}
使用未初始化的内存
如果在栈或堆中分配了未初始化的内存并使用它,可能会导致程序行为异常。某些编译器和 IDE(如 Visual Studio)会用特殊值填充未初始化内存(例如 0xCC 或 0xCD),而0xCCCC和0xCDCD在中文GB2312编码中分别对应“烫”字和“屯”字,因此会显示类似“烫烫烫”或“屯屯屯”的乱码。
void function() {
int a; // a 未初始化
cout << a << endl; // 打印随机值,未定义行为
}