介绍
在本实验中,你将学习如何在 C++ 中处理动态内存分配。你将从使用 new 操作符分配内存开始,然后使用 delete 操作符释放内存。你还将探索如何创建动态数组,使用智能指针如 shared_ptr 和 unique_ptr,并检查内存泄漏。这些技能对于 C++ 编程中的有效内存管理至关重要。
本实验涵盖以下关键步骤:使用 new 操作符分配内存,使用 delete 操作符释放内存,使用 new[] 创建动态数组,使用 delete[] 删除数组,实现 shared_ptr 智能指针,使用 unique_ptr 实现独占所有权,检查内存泄漏,以及处理内存分配失败。
使用 new 操作符分配内存
在这一步骤中,你将学习如何在 C++ 中使用 new 操作符在运行时动态分配内存。动态内存分配允许你创建变量和数组,其大小在程序执行期间确定,相比静态内存分配提供了更大的灵活性。
打开 WebIDE,在 ~/project 目录下创建一个名为 dynamic_memory.cpp 的新文件:
touch ~/project/dynamic_memory.cpp
将以下代码添加到 dynamic_memory.cpp 中:
#include <iostream>
int main() {
// 使用 new 动态分配一个整数
int* dynamicInteger = new int;
// 为动态分配的整数赋值
*dynamicInteger = 42;
// 打印动态分配的整数值
std::cout << "Dynamically allocated integer value: " << *dynamicInteger << std::endl;
// 打印动态分配整数的内存地址
std::cout << "Memory address: " << dynamicInteger << std::endl;
return 0;
}
让我们分解关键概念:
int* dynamicInteger = new int;:new操作符为整数分配内存- 返回指向分配内存的指针
- 在堆(heap)上创建变量,而不是栈(stack)
*dynamicInteger = 42;:- 使用解引用操作符
*赋值 - 将值 42 存储在动态分配的内存中
- 使用解引用操作符
编译并运行程序:
g++ dynamic_memory.cpp -o dynamic_memory
./dynamic_memory
示例输出:
Dynamically allocated integer value: 42
Memory address: 0x55f4e8a042a0
关于 new 操作符的关键点:
- 在运行时动态分配内存
- 返回指向分配内存的指针
- 内存分配在堆上
- 大小可以在运行时确定
- 允许更灵活的内存管理
注意:在接下来的步骤中,你将学习如何正确释放这些动态分配的内存,以防止内存泄漏。
使用 delete 操作符释放内存
在这一步骤中,你将学习如何使用 delete 操作符正确释放 C++ 中动态分配的内存。未能释放内存可能会导致内存泄漏,从而影响程序的性能。
打开 WebIDE,在 ~/project 目录下创建一个名为 memory_release.cpp 的新文件:
touch ~/project/memory_release.cpp
将以下代码添加到 memory_release.cpp 中:
#include <iostream>
int main() {
// 动态分配一个整数
int* dynamicInteger = new int;
// 为动态分配的整数赋值
*dynamicInteger = 42;
// 打印释放内存前的值
std::cout << "Value before release: " << *dynamicInteger << std::endl;
// 使用 delete 释放动态分配的内存
delete dynamicInteger;
// 删除后将指针设置为 nullptr
dynamicInteger = nullptr;
// 演示安全的指针使用
if (dynamicInteger == nullptr) {
std::cout << "Memory has been successfully released" << std::endl;
}
return 0;
}
关于 delete 操作符的关键点:
delete dynamicInteger;:- 释放之前通过
new分配的内存 - 防止内存泄漏
- 将内存释放回系统
- 释放之前通过
dynamicInteger = nullptr;:- 删除后将指针设置为
nullptr - 防止意外使用已释放的内存
- 提供额外的安全检查
- 删除后将指针设置为
编译并运行程序:
g++ memory_release.cpp -o memory_release
./memory_release
示例输出:
Value before release: 42
Memory has been successfully released
重要的内存管理规则:
- 始终为通过
new分配的内存使用delete - 删除后将指针设置为
nullptr - 删除后切勿使用指针
- 每个
new都应有对应的delete
使用 new[] 创建动态数组
在这一步骤中,你将学习如何在 C++ 中使用 new[] 操作符创建动态数组。动态数组允许你在运行时为多个元素分配内存,相比静态数组提供了更大的灵活性。
打开 WebIDE,在 ~/project 目录下创建一个名为 dynamic_array.cpp 的新文件:
touch ~/project/dynamic_array.cpp
将以下代码添加到 dynamic_array.cpp 中:
#include <iostream>
int main() {
// 声明动态数组的大小
int arraySize = 5;
// 使用 new[] 创建一个动态整数数组
int* dynamicArray = new int[arraySize];
// 初始化数组元素
for (int i = 0; i < arraySize; ++i) {
dynamicArray[i] = i * 10;
}
// 打印数组元素
std::cout << "Dynamic Array Contents:" << std::endl;
for (int i = 0; i < arraySize; ++i) {
std::cout << "Element " << i << ": " << dynamicArray[i] << std::endl;
}
return 0;
}
关于动态数组的关键点:
int* dynamicArray = new int[arraySize];:- 为整数数组分配内存
arraySize决定了元素的数量- 内存分配在堆上
- 大小可以在运行时确定
数组初始化和访问:
- 使用标准数组索引
dynamicArray[i] - 可以像普通数组一样操作
- 允许根据运行时条件动态调整大小
- 使用标准数组索引
编译并运行程序:
g++ dynamic_array.cpp -o dynamic_array
./dynamic_array
示例输出:
Dynamic Array Contents:
Element 0: 0
Element 1: 10
Element 2: 20
Element 3: 30
Element 4: 40
重要注意事项:
- 动态数组提供了内存分配的灵活性
- 大小可以在运行时确定
- 记得使用
new[]进行分配,使用delete[]进行释放 - 始终匹配分配和释放方法
使用 delete[] 删除数组
在这一步骤中,你将学习如何使用 delete[] 操作符正确释放为动态数组分配的内存。正确的内存管理对于防止内存泄漏和确保资源的高效利用至关重要。
打开 WebIDE,在 ~/project 目录下创建一个名为 delete_array.cpp 的新文件:
touch ~/project/delete_array.cpp
将以下代码添加到 delete_array.cpp 中:
#include <iostream>
int main() {
// 声明动态数组的大小
int arraySize = 5;
// 使用 new[] 创建一个动态整数数组
int* dynamicArray = new int[arraySize];
// 初始化数组元素
for (int i = 0; i < arraySize; ++i) {
dynamicArray[i] = i * 10;
}
// 打印删除前的数组元素
std::cout << "Array Contents Before Deletion:" << std::endl;
for (int i = 0; i < arraySize; ++i) {
std::cout << "Element " << i << ": " << dynamicArray[i] << std::endl;
}
// 使用 delete[] 释放动态分配的数组
delete[] dynamicArray;
// 删除后将指针设置为 nullptr
dynamicArray = nullptr;
// 演示安全的指针使用
if (dynamicArray == nullptr) {
std::cout << "Dynamic array has been successfully deleted" << std::endl;
}
return 0;
}
关于删除动态数组的关键点:
delete[] dynamicArray;:- 正确释放整个数组的内存
- 必须为通过
new[]分配的数组使用delete[] - 防止内存泄漏
- 为每个数组元素调用析构函数
dynamicArray = nullptr;:- 删除后将指针设置为
nullptr - 防止意外访问已释放的内存
- 提供额外的安全检查
- 删除后将指针设置为
编译并运行程序:
g++ delete_array.cpp -o delete_array
./delete_array
示例输出:
Array Contents Before Deletion:
Element 0: 0
Element 1: 10
Element 2: 20
Element 3: 30
Element 4: 40
Dynamic array has been successfully deleted
重要的内存管理规则:
- 始终为通过
new[]分配的数组使用delete[] - 切勿混用
delete和delete[] - 删除后将指针设置为
nullptr - 每个
new[]都应有对应的delete[]
实现智能指针 shared_ptr
在这一步骤中,你将学习 shared_ptr,这是 C++ 标准库中的一种智能指针,提供自动内存管理和引用计数功能。智能指针有助于防止内存泄漏并简化内存管理。
打开 WebIDE,在 ~/project 目录下创建一个名为 shared_pointer.cpp 的新文件:
touch ~/project/shared_pointer.cpp
将以下代码添加到 shared_pointer.cpp 中:
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass(int value) : data(value) {
std::cout << "Constructor called. Value: " << data << std::endl;
}
~MyClass() {
std::cout << "Destructor called. Value: " << data << std::endl;
}
int getData() const { return data; }
private:
int data;
};
int main() {
// 创建一个指向 MyClass 的 shared_ptr
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>(42);
// 创建另一个指向同一对象的 shared_ptr
std::shared_ptr<MyClass> ptr2 = ptr1;
// 打印引用计数
std::cout << "Reference Count: " << ptr1.use_count() << std::endl;
// 通过 shared_ptr 访问对象
std::cout << "Data from ptr1: " << ptr1->getData() << std::endl;
std::cout << "Data from ptr2: " << ptr2->getData() << std::endl;
// 当指针超出作用域时,内存会自动释放
return 0;
}
关于 shared_ptr 的关键点:
std::make_shared<MyClass>(42):- 创建一个带有动态内存分配的共享指针
- 使用构造函数参数初始化对象
- 比单独的
new和shared_ptr创建更高效
ptr1.use_count():- 返回指向同一对象的
shared_ptr实例数量 - 帮助跟踪对象的引用计数
- 返回指向同一对象的
自动内存管理:
- 当没有
shared_ptr引用对象时,内存会自动释放 - 防止手动内存管理错误
- 当没有
编译并运行程序:
g++ -std=c++11 shared_pointer.cpp -o shared_pointer
./shared_pointer
示例输出:
Constructor called. Value: 42
Reference Count: 2
Data from ptr1: 42
Data from ptr2: 42
Destructor called. Value: 42
shared_ptr 的重要特性:
- 自动内存管理
- 引用计数
- 防止内存泄漏
- 线程安全的引用计数
使用 unique_ptr 实现独占所有权
在这一步骤中,你将学习 unique_ptr,这是一种提供动态分配资源独占所有权的智能指针。与 shared_ptr 不同,unique_ptr 确保同一时间只有一个指针可以拥有资源。
打开 WebIDE,在 ~/project 目录下创建一个名为 unique_pointer.cpp 的新文件:
touch ~/project/unique_pointer.cpp
将以下代码添加到 unique_pointer.cpp 中:
#include <iostream>
#include <memory>
class Resource {
public:
Resource(int value) : data(value) {
std::cout << "Resource created. Value: " << data << std::endl;
}
~Resource() {
std::cout << "Resource destroyed. Value: " << data << std::endl;
}
int getData() const { return data; }
private:
int data;
};
void processResource(std::unique_ptr<Resource> resource) {
std::cout << "Processing resource with value: " << resource->getData() << std::endl;
// 当函数结束时,资源会自动删除
}
int main() {
// 创建一个 unique_ptr
std::unique_ptr<Resource> ptr1 = std::make_unique<Resource>(42);
// 打印资源数据
std::cout << "Resource value: " << ptr1->getData() << std::endl;
// 使用 std::move 转移所有权
std::unique_ptr<Resource> ptr2 = std::move(ptr1);
// ptr1 现在为 nullptr
if (ptr1 == nullptr) {
std::cout << "ptr1 is now null after moving ownership" << std::endl;
}
// 将 unique_ptr 传递给函数(所有权被转移)
processResource(std::move(ptr2));
return 0;
}
关于 unique_ptr 的关键点:
std::make_unique<Resource>(42):- 创建一个带有动态内存分配的独占指针
- 确保资源的独占所有权
std::move(ptr1):- 转移资源的所有权
- 原始指针变为
nullptr - 防止多个指针拥有同一资源
自动内存管理:
- 当
unique_ptr超出作用域时,资源会自动删除 - 无需手动管理内存
- 当
编译并运行程序:
g++ -std=c++14 unique_pointer.cpp -o unique_pointer
./unique_pointer
示例输出:
Resource created. Value: 42
Resource value: 42
ptr1 is now null after moving ownership
Processing resource with value: 42
Resource destroyed. Value: 42
unique_ptr 的重要特性:
- 独占所有权
- 自动内存管理
- 不能被复制,只能被移动
- 防止资源泄漏
检查内存泄漏
在这一步骤中,你将学习如何使用 Valgrind 这一强大的内存调试工具来检测和防止 C++ 中的内存泄漏。当动态分配的内存未被正确释放时,就会发生内存泄漏,导致程序消耗越来越多的内存。
首先,在终端中安装 Valgrind:
sudo apt update
sudo apt install -y valgrind
在 WebIDE 中创建一个内存泄漏示例,在 ~/project 目录下创建 memory_leak.cpp 文件:
touch ~/project/memory_leak.cpp
将以下代码添加到 memory_leak.cpp 中:
#include <iostream>
class Resource {
public:
Resource(int value) : data(value) {
std::cout << "Resource created: " << data << std::endl;
}
~Resource() {
std::cout << "Resource destroyed: " << data << std::endl;
}
private:
int data;
};
void createMemoryLeak() {
// 这里会导致内存泄漏,因为资源未被删除
Resource* leak = new Resource(42);
// 缺失:delete leak;
}
int main() {
// 模拟多次内存泄漏
for (int i = 0; i < 3; ++i) {
createMemoryLeak();
}
return 0;
}
使用调试符号编译程序:
g++ -g memory_leak.cpp -o memory_leak
运行 Valgrind 以检测内存泄漏:
valgrind --leak-check=full ./memory_leak
Valgrind 示例输出:
==1988== Memcheck, a memory error detector
==1988== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==1988== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==1988== Command: ./memory_leak
==1988==
Resource created: 42
Resource created: 42
Resource created: 42
==1988==
==1988== HEAP SUMMARY:
==1988== in use at exit: 12 bytes in 3 blocks
==1988== total heap usage: 5 allocs, 2 frees, 73,740 bytes allocated
==1988==
==1988== 12 bytes in 3 blocks are definitely lost in loss record 1 of 1
==1988== at 0x4849013: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==1988== by 0x109241: createMemoryLeak() (memory_leak.cpp:19)
==1988== by 0x109299: main (memory_leak.cpp:26)
==1988==
==1988== LEAK SUMMARY:
==1988== definitely lost: 12 bytes in 3 blocks
==1988== indirectly lost: 0 bytes in 0 blocks
==1988== possibly lost: 0 bytes in 0 blocks
==1988== still reachable: 0 bytes in 0 blocks
==1988== suppressed: 0 bytes in 0 blocks
==1988==
==1988== For lists of detected and suppressed errors, rerun with: -s
==1988== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
如你所见,Valgrind 检测到 12 字节的内存泄漏,分布在 3 个块中。createMemoryLeak() 中创建的 Resource 对象未被删除,导致了内存泄漏。
要修复内存泄漏,修改代码以正确删除资源:
void createMemoryLeak() {
// 正确删除动态分配的资源
Resource* leak = new Resource(42);
delete leak; // 添加此行以防止内存泄漏
}
重新编译并使用 Valgrind 运行程序,以验证内存泄漏已修复:
g++ -g memory_leak.cpp -o memory_leak
valgrind --leak-check=full ./memory_leak
==2347== Memcheck, a memory error detector
==2347== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==2347== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==2347== Command: ./memory_leak
==2347==
Resource created: 42
Resource destroyed: 42
Resource created: 42
Resource destroyed: 42
Resource created: 42
Resource destroyed: 42
==2347==
==2347== HEAP SUMMARY:
==2347== in use at exit: 0 bytes in 0 blocks
==2347== total heap usage: 5 allocs, 5 frees, 73,740 bytes allocated
==2347==
==2347== All heap blocks were freed -- no leaks are possible
==2347==
==2347== For lists of detected and suppressed errors, rerun with: -s
==2347== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
关于内存泄漏的关键点:
- 始终
delete动态分配的内存 - 使用智能指针如
unique_ptr和shared_ptr - 使用 Valgrind 检测内存泄漏
- 使用
-g标志编译以获得更好的调试信息
处理内存分配失败
在这一步骤中,你将学习如何在 C++ 中处理内存分配失败。当动态内存分配失败时,new 操作符会抛出 std::bad_alloc 异常,你可以捕获并优雅地处理该异常。
打开 WebIDE,在 ~/project 目录下创建一个名为 memory_allocation.cpp 的新文件:
touch ~/project/memory_allocation.cpp
将以下代码添加到 memory_allocation.cpp 中:
#include <iostream>
#include <new>
#include <limits>
void demonstrateMemoryAllocation() {
try {
// 尝试分配大量内存
const size_t largeSize = 1000000000000; // 1 万亿个整数
int* largeArray = new int[largeSize];
// 如果分配失败,此行将不会执行
std::cout << "Memory allocation successful" << std::endl;
// 清理分配的内存
delete[] largeArray;
}
catch (const std::bad_alloc& e) {
// 处理内存分配失败
std::cerr << "Memory allocation failed: " << e.what() << std::endl;
}
}
void safeMemoryAllocation() {
// 使用 std::nothrow 防止抛出异常
int* safeArray = new(std::nothrow) int[1000000];
if (safeArray == nullptr) {
std::cerr << "Memory allocation failed silently" << std::endl;
return;
}
// 使用分配的内存
std::cout << "Safe memory allocation successful" << std::endl;
// 清理分配的内存
delete[] safeArray;
}
int main() {
std::cout << "Demonstrating memory allocation failure handling:" << std::endl;
// 方法 1:使用异常处理
demonstrateMemoryAllocation();
// 方法 2:使用 std::nothrow
safeMemoryAllocation();
return 0;
}
关于内存分配失败处理的关键点:
使用
std::bad_alloc进行异常处理:try-catch块捕获内存分配异常- 提供详细的错误信息
- 防止程序崩溃
std::nothrow分配:- 防止抛出异常
- 在分配失败时返回
nullptr - 允许静默处理失败
编译并运行程序:
g++ memory_allocation.cpp -o memory_allocation
./memory_allocation
示例输出:
Demonstrating memory allocation failure handling:
Memory allocation failed: std::bad_alloc
Safe memory allocation successful
重要的内存分配策略:
- 始终检查分配失败
- 使用异常处理或
std::nothrow - 实现备用机制
- 避免分配极大的内存块
总结
在本实验中,你将学习如何在 C++ 中处理动态内存分配。首先,你将学习使用 new 操作符在运行时动态分配内存,相比静态分配,这提供了更灵活的内存管理。接着,你将学习如何使用 delete 操作符正确释放这些动态分配的内存,以防止内存泄漏。此外,你还将探索如何使用 new[] 创建动态数组,并使用 delete[] 删除它们。实验还涵盖了使用智能指针(如 shared_ptr 和 unique_ptr)来简化内存管理并避免常见问题。最后,你将学习如何检查和处理内存分配失败的技术。



