动态内存分配:new 和 new[]
在 C++ 中,可以使用 new 和 new[] 运算符在运行时动态地分配内存。动态分配的内存位于堆(heap)区,与栈(stack)区的自动分配内存不同,需要手动释放。
new: 用于分配单个对象的内存。new[]: 用于分配数组的内存。
代码示例:
#include <iostream>
int main() {
// 使用 new 分配一个整数
int *p = new int;
*p = 10;
std::cout << "*p: " << *p << std::endl;
delete p; // 释放单个对象的内存
// 使用 new[] 分配一个包含 5 个整数的数组
int *arr = new int[5];
for (int i = 0; i < 5; ++i) {
arr[i] = i * 2;
}
std::cout << "动态分配的数组:";
for (int i = 0; i < 5; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl; // 输出:0 2 4 6 8
delete[] arr; // 释放数组内存
return 0;
}
解释:
int *p = new int;在堆上分配一块足以存储一个int的内存,并返回该内存的地址,赋值给指针p。int *arr = new int[5];在堆上分配一块足以存储 5 个int的连续内存,并返回首元素的地址。
C 语言中的对应操作:
malloc(): 用于动态分配内存。calloc(): 用于动态分配并初始化为零。realloc(): 用于重新分配内存。free(): 用于释放malloc,calloc, 或realloc分配的内存。
重要提示: new 和 delete 必须成对使用,new[] 和 delete[] 必须成对使用,否则会导致内存泄漏或程序崩溃。
3. 动态内存释放:delete 和 delete[]
使用 new 或 new[] 分配的内存,在不再使用时必须使用 delete 或 delete[] 进行释放。
delete: 用于释放通过new分配的单个对象的内存。delete[]: 用于释放通过new[]分配的数组的内存。
4. 内存越界及其危害
内存越界指的是程序试图访问不属于其分配范围的内存区域。这是一种非常危险的行为,可能导致:
- 程序崩溃: 操作系统可能会阻止程序访问非法内存。
- 数据损坏: 覆盖其他变量或数据结构的内容,导致程序逻辑错误。
- 安全漏洞: 在某些情况下,越界访问可能被恶意利用。
代码示例:
#include <iostream>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
for (int i = 0; i <= 5; ++i) { // 错误:循环条件应为 i < 5
std::cout << arr[i] << " ";
}
std::cout << std::endl; // 最后一个访问 arr[5] 越界
return 0;
}
5. 内存泄漏及其危害
内存泄漏是指程序在动态分配内存后,忘记或未能释放不再使用的内存。随着程序的运行,泄漏的内存会越来越多,最终可能导致:
- 程序性能下降: 可用内存减少,系统需要更频繁地进行内存管理。
- 系统崩溃: 当所有可用内存都被耗尽时。
代码示例:
#include <iostream>
void memoryLeaker() {
int *ptr = new int[1000];
// 忘记 delete[] ptr;
}
int main() {
for (int i = 0; i < 100; ++i) {
memoryLeaker(); // 每次调用都会泄漏 4000 字节的内存 (假设 int 为 4 字节)
}
// 程序结束时,泄漏的内存由操作系统回收,但长期运行的程序可能会耗尽内存
return 0;
}
6. 指针的安全使用原则
指针是 C++ 中强大但容易出错的特性。安全使用指针至关重要:
(1) 初始化: 指针在声明时应初始化为一个有效的地址或空指针 (nullptr)。
(2) 空指针检查: 在解引用指针之前,务必检查它是否为 nullptr,避免访问无效内存。
(3) 匹配的 new 和 delete: 确保每个 new 分配的内存都有对应的 delete 释放,new[] 对应 delete[]。
(4) 避免重复释放: 不要释放已经释放过的内存,这会导致程序崩溃。
(5) 释放后置空: 释放内存后,立即将指针设置为 nullptr,防止产生悬挂指针(dangling pointer)。
(6) 避免悬挂指针: 悬挂指针指向的内存已经被释放,访问它会导致未定义行为。
(7) 谨慎使用原始指针: 考虑使用智能指针来管理动态分配的内存,以减少手动管理的错误。
代码示例:
#include <iostream>
int main() {
int *ptr = nullptr; // 初始化为空指针
if (ptr != nullptr) {
*ptr = 10; // 不会执行,避免了访问空指针
}
ptr = new int;
if (ptr != nullptr) {
*ptr = 20;
std::cout << "*ptr: " << *ptr << std::endl;
delete ptr;
ptr = nullptr; // 释放后置空
}
// ... 后续不再使用已释放的 ptr
return 0;
}