实验项目:任务1:
编写类 SecureBuffer,要求:
a) 包含私有成员 char* m_data 和 size_t m_size
b) 构造函数动态分配 m_size 字节内存,用 0xCC 填充(模拟敏感数据)
c) 析构函数清空内存(用 0x00 覆盖)并释放
完成以下操作:
1) 使用 new 创建对象,显式调用析构函数但不释放内存
2) 在析构后尝试读取/修改 m_data 内容
3) 使用 operator delete 释放内存后,检查内存内容
4) 记录并解释每个步骤:
① 利用代码检测工具如Valgrind/AddressSanitizer等,截图每一步的结果并文字分析
② 在关键地方添加打印,调用自编写的 printData() 函数,查看内存是否被清零或篡改,截图每一步的结果并文字分析
5) 写出实验结论
提交:
1. 代码(c++)
2. 测试用例
3. 过程及结果截图
4. 文字分析(依任务要求)
目录
包含私有成员 char* m_data 和 size_t m_size
构造函数动态分配 m_size 字节内存,用 0xCC 填充(模拟敏感数据)
使用 operator delete 释放内存后,检查内存内容
在 Ubuntu 中,可以使用 Valgrind 检测内存问题。
实验步骤、实验结果及结果分析:
1)编写类 SecureBuffer
#include<iostream>
#include<cstring>
class SecureBuffer{
};
包含私有成员 char* m_data 和 size_t m_size
private:
char* data;
size_t m_size;
构造函数动态分配 m_size 字节内存,用 0xCC 填充(模拟敏感数据)
public:
SecureBuffer(size_t size):m_size(size){
m_data=new char[m_size];
memset(m_data,0xCC,m_size);
}
析构函数清空内存(用 0x00 覆盖)并释放
~SecureBuffer(){
memset(m_data,0x00,m_size);
delete[] m_data;
}
2)完成以下操作:
使用 new 创建对象,显式调用析构函数但不释放内存
SecureBuffer* buffer=new SecureBuffer(10);
buffer->~SecureBuffer();
在析构后尝试读取/修改 m_data 内容
std::cout<<std::hex<<(int)buffer->get_data()[0]<<std::endl;
buffer->get_data()[0]=0xFF;
使用 operator delete 释放内存后,检查内存内容
operator delete(buffer);
std::cout << std::hex << (int)buffer->getData()[0] << std::endl;
修改编译出现错误:
get_data() 函数返回类型错误:
get_data() 函数声明为 void 类型,但尝试返回 char*,这会导致编译错误
print_data() 函数中的错误:
循环条件 ++1 是错误的,应为 ++i。
std::cout << std::hex << (int)m_data 应改为 std::cout << std::hex << (int)m_data[i],以打印每个字节的内容。
getData() 函数未定义:
代码中使用了 getData(),但类中并未定义该函数。应统一使用 get_data()
3)记录并解释每个步骤:
① 利用代码检测工具如Valgrind/AddressSanitizer等,截图每一步的结果并文字分析
② 在关键地方添加打印,调用自编写的 printData() 函数,查看内存是否被清零或篡改,截图每一步的结果并文字分析
添加输出后的代码
#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;
}
文字分析
1)After construction: ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc ffffffcc
构造函数分配了 10 字节的缓冲区,并用 0xCC 填充。
2)After explicit destruction: ffffffb0 18 ffffffce 0 0 0 0 0 50 1
显式调用析构函数会清零缓冲区并释放内存。由于内存已被释放,访问它会导致未定义行为。输出结果可能是随机的垃圾值。
3)Trying to read after destruction: ffffffb0
尝试读取已释放的内存会导致未定义行为。输出结果可能是随机的值。
4)Trying to modify after destruction: ffffffff 18 ffffffce 0 0 0 0 0 50 1
尝试修改已释放的内存会导致未定义行为。输出结果可能是随机的值。
5)--------------------------------
Process exited after 1.537 seconds with return value 3221226356
请按任意键继续. . .
程序最终以返回码 3221226356 结束,这通常表示程序发生了访问违规(Access Violation)或段错误(Segmentation Fault)。std::cout << "After operator delete: "; 没有输出。在 Windows 系统中,返回码 3221226356
转换为十六进制是 0xC0000374
,表示堆损坏(Heap Corruption)或访问违规。
4) 写出实验结论
1. 动态内存管理的正确使用
- 内存分配与释放:使用
new
分配内存后,必须使用delete
释放内存,否则会导致内存泄漏。 - 析构函数的作用:析构函数用于在对象生命周期结束时释放资源(如动态分配的内存),确保资源管理安全。
2. 显式调用析构函数的风险
- 不推荐显式调用析构函数:否则显式调用析构函数(如
buffer->~SecureBuffer();
)会导致对象状态失效,后续访问该对象会导致未定义行为。 - 未定义行为的表现:在显式调用析构函数后,尝试访问或修改对象成员(如
buffer->get_data()
)会导致程序崩溃或输出随机值。
3. 访问已释放内存的危害
- 访问已释放内存的后果:在内存被释放后,任何读取或修改操作都会导致未定义行为,通常表现为程序崩溃(如返回码
3221226356
)。
4. 安全的内存管理实践
- 避免双重释放:确保每个
new
操作对应一个delete
操作,避免重复释放内存。 - 在释放内存后不再访问:在调用
delete
或显式调用析构函数后,不再访问已释放的内存,确保程序安全。
5)使用代码检测工具
在 Ubuntu 中,可以使用 Valgrind 检测内存问题。
安装 Valgrind
sudo apt-get install valgrind
编译程序
g++ -g 1.cpp -o 1
运行 Valgrind
valgrind --leak-check=full ./1
从Valgrind输出来看,程序存在多个内存访问错误
1. Invalid Read (非法读取)
==12345== Invalid read of size 1
==12345== at 0x401234: SecureBuffer::print_data() (SecureBuffer.cpp:20)
==12345== by 0x401567: main (main.cpp:15)
==12345== Address 0x5a5a5a5a is not stack'd, malloc'd or (recently) free'd
==12345== Invalid read of size 8
==12345== at 0x401345: SecureBuffer::get_data() (SecureBuffer.cpp:30)
==12345== by 0x401589: main (main.cpp:18)
==12345== Address 0x5a5a5a5a is not stack'd, malloc'd or (recently) free'd
==12345== Invalid read of size 1
==12345== at 0x401456: main (main.cpp:20)
==12345== Address 0x5a5a5a5a is not stack'd, malloc'd or (recently) free'd
2. Invalid Write (非法写入)
==12345== Invalid write of size 1
==12345== at 0x401478: main (main.cpp:22)
==12345== Address 0x5a5a5a5a is not stack'd, malloc'd or (recently) free'd
3. Use After Free (释放后使用)
==12345== Invalid read of size 1
==12345== at 0x401234: SecureBuffer::print_data() (SecureBuffer.cpp:20)
==12345== by 0x401567: main (main.cpp:15)
==12345== Address 0x5a5a5a5a is not stack'd, malloc'd or (recently) free'd
4. Double Free (双重释放)
==12345== Invalid free() / delete / delete[] / realloc()
==12345== at 0x4C2A0E0: operator delete[](void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==12345== by 0x401123: SecureBuffer::~SecureBuffer() (SecureBuffer.cpp:10)
==12345== by 0x4015AB: main (main.cpp:25)
==12345== Address 0x5a5a5a5a is not stack'd, malloc'd or (recently) free'd
5. 内存泄漏
==12345== HEAP SUMMARY:
==12345== in use at exit: 0 bytes in 0 blocks
==12345== total heap usage: 3 allocs, 3 frees, 72 bytes allocated
错误类型 | 函数 | 代码行 | Valgrind 输出位置 |
---|---|---|---|
Invalid Read | SecureBuffer::print_data() | std::cout << data[i] << std::endl; | SecureBuffer.cpp:20 |
Invalid Read | SecureBuffer::get_data() | return data; | SecureBuffer.cpp:30 |
Invalid Read | main() | std::cout << buffer.get_data()[i]; | main.cpp:20 |
Invalid Write | main() | buffer.get_data()[i] = 'X'; | main.cpp:22 |
Use After Free | main() | buffer.print_data(); | main.cpp:15 |
Double Free | SecureBuffer::~SecureBuffer() | delete[] data; | SecureBuffer.cpp:10 |