安全编码课程 实验5 动态内存(3)
实验项目:任务3
任务1和任务2基础上改写 SecureBuffer 类,完成安全编程,使用 Valgrind/AddressSanitizer或其他代码检测工具检测问题:
手动编写函数,实现 SafeDelete(T& ptr)*
功能点:
清空内存内容(如 memset(ptr, 0, size))
安全释放 delete
自动置空,避免悬垂指针
防止重复释放
拷贝构造更改为移动构造
为 SecureBuffer 类增加移动构造函数和移动赋值运算符
使用智能指针优化代码,降低悬垂指针风险,彻底杜绝手动 delete,更安全
使用 std::unique_ptr 管理 SecureBuffer
用 std::shared_ptr 进行引用计数(新增)
引入容器存储 std::shared_ptr,进行资源管理
功能点
使用 std::vector<std::shared_ptr<SecureBuffer>> 统一管理多个 SecureBuffer
确保对象在所有引用结束后自动释放
输出容器内容,观察 shared_ptr 的引用计数
测试要求(利用任务1的打印方法打印出内存内容):
- 使用 SafeDelete 测试
- 调用SecureBuffer类的修改方法(在任务1中已经实现),修改对象,之后进行移动构造测试移动语义
- 使用 std::unique_ptr 进行内存管理
- 使用 std::shared_ptr 进行共享引用管理
- 使用 std::vector 容器存储 shared_ptr
提交:
1. 代码(c++)
2. 测试用例
3. 过程及结果截图
4. 文字分析,要求:回顾和内存管理相关的3次实验内容,可以从这个角度进行总结:
手动管理内存(new/delete包括构造析构和分配释放)-> SafeDelete
拷贝构造 -> 移动构造
裸指针 -> 智能指针(unique_ptr、shared_ptr)
容器
目录
实验项目:任务3
实验步骤、实验结果及结果分析:
1. SafeDelete 实现
初始实现
添加内存清空
显式调用析构函数
2. 移动语义实现
添加移动构造函数和移动赋值运算符
3. 智能指针使用
使用 std::unique_ptr
4. 容器管理
提交:
1. 代码(c++)
2. 测试用例
3. 过程及结果截图
4. 文字分析
实验步骤、实验结果及结果分析:
// 原始代码
#include<iostream>
#include<cstring>
class SecureBuffer{
private:
char* m_data;
size_t m_size;
public:
SecureBuffer(size_t size):m_size(size){
m_data=new char[m_size];
memset(m_data,0xCC,m_size);
}
~SecureBuffer(){
memset(m_data,0x00,m_size);
delete[] m_data;
}
void print_data(){
for(size_t i=0;i<m_size;++i){
std::cout<<std::hex<<(int)m_data[i]<<" ";
}
std::cout<<std::endl;
}
char* get_data(){
return m_data;
}
};
int main(){
// 1
SecureBuffer* buffer=new SecureBuffer(10);
std::cout << "After construction: ";
buffer->print_data();
buffer->~SecureBuffer();
std::cout << "After explicit destruction: ";
buffer->print_data();
// 2
std::cout << "Trying to read after destruction: ";
std::cout<<std::hex<<(int)buffer->get_data()[0]<<std::endl;
std::cout << "Trying to modify after destruction: ";
buffer->get_data()[0]=0xFF;
buffer->print_data();
// 3
operator delete(buffer);
std::cout << "After operator delete: ";
std::cout << std::hex << (int)buffer->get_data()[0] << std::endl;
}
#include <iostream>
#include <cstring>
#include <vector>
#include <memory> // 引入智能指针头文件
class SecureBuffer {
private:
std::unique_ptr<char[]> m_data; // 使用智能指针管理内存
size_t m_size;
public:
// 构造函数
SecureBuffer(size_t size) : m_size(size), m_data(std::make_unique<char[]>(m_size)) {
std::memset(m_data.get(), 0xCC, m_size);
}
// 析构函数(无需手动释放内存)
~SecureBuffer() {
std::memset(m_data.get(), 0x00, m_size); // 清空内存为 0x00
}
// 打印数据
void print_data() const {
for (size_t i = 0; i < m_size; ++i) {
std::cout << std::hex << (int)m_data[i] << " ";
}
std::cout << std::endl;
}
};
int main() {
std::vector<std::unique_ptr<SecureBuffer>> buffers; // 使用智能指针
for (int i = 0; i < 10000; ++i) {
buffers.push_back(std::make_unique<SecureBuffer>(10)); // 自动管理内存
}
std::cout << "Created 10000 SecureBuffer objects with smart pointers." << std::endl;
return 0; // 程序退出时,智能指针会自动释放内存
}
1. SafeDelete 实现
手动编写函数,实现 SafeDelete(T& ptr)*
功能点:
清空内存内容(如 memset(ptr, 0, size))
安全释放 delete
自动置空,避免悬垂指针
防止重复释放
初始实现
- 自动置空指针,避免悬垂指针
- 防止重复释放(通过检查指针是否为 null)
template<typename T>
void SafeDelete(T*& ptr) {
if (ptr != nullptr) {
delete ptr;
ptr = nullptr;
}
}
添加内存清空
template<typename T>
void SafeDelete(T*& ptr) {
if (ptr != nullptr) {
// 清空内存内容
memset(ptr, 0, sizeof(T));
delete ptr;
ptr = nullptr;
}
}
显式调用析构函数
template<typename T>
void SafeDelete(T*& ptr) {
if (ptr != nullptr) {
// 显式调用析构函数
ptr->~T();
// 清空内存内容
memset(ptr, 0, sizeof(T));
// 使用 operator delete 释放内存
::operator delete(ptr);
ptr = nullptr;
}
}
实现了 SafeDelete
模板函数,具有以下安全特性:
- 清空内存内容(使用 memset)
- 安全释放内存(先调用析构函数,再 operator delete)
- 自动置空指针,避免悬垂指针
- 防止重复释放(通过检查指针是否为 null)
2. 移动语义实现
拷贝构造更改为移动构造
为 SecureBuffer 类增加移动构造函数和移动赋值运算符
class SecureBuffer {
public:
SecureBuffer(size_t size) : size_(size), data_(new uint8_t[size]) {}
~SecureBuffer() {
if (data_) {
memset(data_, 0, size_);
delete[] data_;
data_ = nullptr;
size_ = 0;
}
}
// 禁用拷贝构造和拷贝赋值
SecureBuffer(const SecureBuffer&) = delete;
SecureBuffer& operator=(const SecureBuffer&) = delete;
private:
size_t size_;
uint8_t* data_;
};
添加移动构造函数和移动赋值运算符
class SecureBuffer {
public:
// ... 原有构造函数和析构函数 ...
// 移动构造函数
SecureBuffer(SecureBuffer&& other) noexcept
: size_(other.size_), data_(other.data_) {
other.size_ = 0;
other.data_ = nullptr;
}
// 移动赋值运算符
SecureBuffer& operator=(SecureBuffer&& other) noexcept {
if (this != &other) {
// 清理当前对象资源
if (data_) {
memset(data_, 0, size_);
delete[] data_;
}
// 转移资源所有权
size_ = other.size_;
data_ = other.data_;
// 置空源对象
other.size_ = 0;
other.data_ = nullptr;
}
return *this;
}
private:
size_t size_;
uint8_t* data_;
};
为 SecureBuffer 类增加了:
- 移动构造函数:转移资源所有权,将源对象置空
- 移动赋值运算符:安全转移资源,先清理当前对象资源
- 禁用拷贝构造和拷贝赋值(= delete)
3. 智能指针使用
使用智能指针优化代码,降低悬垂指针风险,彻底杜绝手动 delete,更安全
使用 std::unique_ptr 管理 SecureBuffer
用 std::shared_ptr 进行引用计数(新增)
使用 std::unique_ptr
void demoUniquePtr() {
// 创建 unique_ptr 管理的 SecureBuffer
std::unique_ptr<SecureBuffer> buffer(new SecureBuffer(1024));
// 转移所有权
std::unique_ptr<SecureBuffer> buffer2 = std::move(buffer);
// buffer 现在为空
assert(buffer == nullptr);
// 自动释放,无需手动调用 delete
}
使用 std::shared_ptr 和 std::make_shared
void demoSharedPtr() {
// 使用 make_shared 创建 shared_ptr
auto buffer1 = std::make_shared<SecureBuffer>(1024);
{
// 共享所有权
auto buffer2 = buffer1;
// 引用计数为2
assert(buffer1.use_count() == 2);
}
// buffer2 离开作用域,引用计数减为1
assert(buffer1.use_count() == 1);
// 自动释放,当最后一个引用消失时
}
- 使用
std::unique_ptr
管理 SecureBuffer,确保独占所有权和自动释放 - 使用
std::shared_ptr
实现引用计数,允许多个所有者共享资源 - 使用
std::make_shared
创建 shared_ptr,更高效且安全
4. 容器管理
引入容器存储 std::shared_ptr,进行资源管理
功能点
使用 std::vector<std::shared_ptr<SecureBuffer>> 统一管理多个 SecureBuffer
确保对象在所有引用结束后自动释放
输出容器内容,观察 shared_ptr 的引用计数
使用 vector 管理 shared_ptr
void demoContainer() {
std::vector<std::shared_ptr<SecureBuffer>> buffers;
// 添加几个 SecureBuffer
buffers.push_back(std::make_shared<SecureBuffer>(256));
buffers.push_back(std::make_shared<SecureBuffer>(512));
buffers.push_back(std::make_shared<SecureBuffer>(1024));
// 观察引用计数
auto buffer = buffers[0];
std::cout << "Reference count: " << buffer.use_count() << std::endl; // 2
{
auto localCopy = buffer;
std::cout << "Reference count: " << buffer.use_count() << std::endl; // 3
}
std::cout << "Reference count: " << buffer.use_count() << std::endl; // 2
// 从容器中移除
buffers.erase(buffers.begin());
std::cout << "Reference count: " << buffer.use_count() << std::endl; // 1
// 最后一个引用消失时自动释放
}
使用 std::vector<std::shared_ptr<SecureBuffer>>
统一管理多个 SecureBuffer:
- 可以观察 shared_ptr 的引用计数变化
- 确保对象在所有引用结束后自动释放
- 演示了共享指针的使用场景
测试要求(利用任务1的打印方法打印出内存内容):
- 使用 SafeDelete 测试
- 调用SecureBuffer类的修改方法(在任务1中已经实现),修改对象,之后进行移动构造测试移动语义
- 使用 std::unique_ptr 进行内存管理
- 使用 std::shared_ptr 进行共享引用管理
- 使用 std::vector 容器存储 shared_ptr
提交:
1. 代码(c++)
#include <iostream>
#include <memory>
#include <vector>
#include <cstring>
// SafeDelete
template<typename T>
void SafeDelete(T*& ptr) {
if (ptr != nullptr) {
ptr->~T(); // 显式调用析构函数
std::memset(ptr, 0, sizeof(T)); // 清空内存内容
::operator delete(ptr); // 使用 operator delete 释放内存
ptr = nullptr; // 置空指针
}
}
// SecureBuffer 类
class SecureBuffer {
public:
SecureBuffer(size_t size) : size_(size), data_(new uint8_t[size]) {
std::memset(data_, 0xCC, size_); // 初始化填充 0xCC
}
~SecureBuffer() {
if (data_) {
std::memset(data_, 0, size_); // 安全清理
delete[] data_;
data_ = nullptr;
size_ = 0;
}
}
// 禁用拷贝构造和拷贝赋值
SecureBuffer(const SecureBuffer&) = delete;
SecureBuffer& operator=(const SecureBuffer&) = delete;
// 移动构造函数
SecureBuffer(SecureBuffer&& other) noexcept
: size_(other.size_), data_(other.data_) {
other.size_ = 0;
other.data_ = nullptr;
}
// 移动赋值运算符
SecureBuffer& operator=(SecureBuffer&& other) noexcept {
if (this != &other) {
// 清理当前对象资源
if (data_) {
std::memset(data_, 0, size_);
delete[] data_;
}
// 转移资源所有权
size_ = other.size_;
data_ = other.data_;
// 置空源对象
other.size_ = 0;
other.data_ = nullptr;
}
return *this;
}
// 打印缓冲区内容(实验一中实现过)
void print_data() const {
for (size_t i = 0; i < size_; ++i) {
std::cout << std::hex << (int)data_[i] << " ";
}
std::cout << std::endl;
}
// 修改缓冲区内容
// (实验一中memset(m_data,0xCC,m_size);这里写成一个方法)
void modify_data(uint8_t value) {
if (data_) {
std::memset(data_, value, size_);
}
}
size_t size() const { return size_; }
private:
size_t size_;
uint8_t* data_;
};
2. 测试用例
// 测试用例
int main() {
// 1. 测试 SafeDelete
{
std::cout << "=== Testing SafeDelete ===" << std::endl;
SecureBuffer* buffer = new SecureBuffer(5);
buffer->print_data(); // 输出初始值(0xCC)
buffer->modify_data(0xFF);
buffer->print_data(); // 输出修改后的值(0xFF)
SafeDelete(buffer); // 安全删除
std::cout << "After SafeDelete: " << (buffer == nullptr ? "Success" : "Failed") << std::endl;
}
// 2. 测试移动语义
{
std::cout << "\n=== Testing Move Semantics ===" << std::endl;
SecureBuffer buffer1(5);
buffer1.modify_data(0xAA);
std::cout << "Buffer1 before move: ";
buffer1.print_data();
SecureBuffer buffer2 = std::move(buffer1); // 移动构造
std::cout << "Buffer2 after move: ";
buffer2.print_data();
std::cout << "Buffer1 after move (should be empty): " << buffer1.size() << std::endl;
}
// 3. 测试 std::unique_ptr
{
std::cout << "\n=== Testing std::unique_ptr ===" << std::endl;
std::unique_ptr<SecureBuffer> uniqueBuffer = std::make_unique<SecureBuffer>(3);
uniqueBuffer->modify_data(0x11);
uniqueBuffer->print_data();
// unique_ptr 会自动释放内存
}
// 4. 测试 std::shared_ptr
{
std::cout << "\n=== Testing std::shared_ptr ===" << std::endl;
std::shared_ptr<SecureBuffer> sharedBuffer1 = std::make_shared<SecureBuffer>(4);
sharedBuffer1->modify_data(0x22);
sharedBuffer1->print_data();
std::shared_ptr<SecureBuffer> sharedBuffer2 = sharedBuffer1; // 共享所有权
std::cout << "Use count: " << sharedBuffer2.use_count() << std::endl;
}
// 5. 测试 std::vector<std::shared_ptr<SecureBuffer>>
{
std::cout << "\n=== Testing std::vector<std::shared_ptr<SecureBuffer>> ===" << std::endl;
std::vector<std::shared_ptr<SecureBuffer>> bufferVec;
bufferVec.push_back(std::make_shared<SecureBuffer>(2));
bufferVec.push_back(std::make_shared<SecureBuffer>(3));
bufferVec.push_back(std::make_shared<SecureBuffer>(4));
for (const auto& buf : bufferVec) {
buf->print_data();
}
}
return 0;
}
3. 过程及结果截图
in use at exit: 0 bytes
:程序结束时所有动态分配的内存均被释放。18 allocs, 18 frees
:内存分配和释放完全匹配,没有泄漏。ERROR SUMMARY: 0
:没有非法内存访问、未初始化内存使用等问题。
4. 文字分析
要求:回顾和内存管理相关的3次实验内容,可以从这个角度进行总结:
手动管理内存(new/delete包括构造析构和分配释放)-> SafeDelete
拷贝构造 -> 移动构造
裸指针 -> 智能指针(unique_ptr、shared_ptr)
容器
手动管理容易导致内存泄漏、悬垂指针或敏感数据残留,而 SafeDelete
通过安全释放、内存清零和指针置空,提高了安全性。
深拷贝在涉及大对象时性能较差,而移动语义通过直接转移资源所有权,避免了不必要的复制,提升了效率,尤其适用于临时对象或智能指针(如 std::unique_ptr
)。
裸指针需要手动管理生命周期,容易出错,而智能指针(std::unique_ptr
独占所有权、std::shared_ptr
共享所有权)结合容器(如 std::vector
)可以自动管理动态对象的生命周期,减少内存泄漏风险,同时利用移动语义优化性能。