1. 动态分配内存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| #include <iostream> #include <cstring> using namespace std;
class SecureBuffer { public: char *m_data; size_t m_size; SecureBuffer(size_t m_size) { m_data = new char[m_size]; std::memset(m_data, 0xCC, m_size); }
~SecureBuffer() { if (m_data) { cout << "memset 0"; std::memset(m_data, 0x00, m_size); delete[] m_data; } } };
int main() { SecureBuffer *sec = new SecureBuffer(12); cout << "test" << sec->m_data << endl; delete sec; return 0; }
|
2. 问题描述
2.1 拷贝函数
浅拷贝是两个对象但是共用一个内存 ,深拷贝是两个对象两个内存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| #include <iostream> #include <cstring> #include <iomanip> using namespace std;
class SecureBuffer { public: char *m_data; size_t m_size;
SecureBuffer(size_t size) : m_size(size) { m_data = new char[m_size]; memset(m_data, 0xCC, m_size); cout << "Constructor: " << m_size << endl; }
SecureBuffer(const SecureBuffer &other) : m_size(other.m_size) { m_data = new char[m_size]; std::memcpy(m_data, other.m_data, m_size); std::cout << "深拷贝构造函数被调用" << std::endl; }
~SecureBuffer() { cout << "Destructor: " << endl; if (m_data) { memset(m_data, 0x00, m_size); } }
void printData(const string &step) { cout << step << endl; cout << "Content: "; for (size_t i = 0; i < min(m_size, (size_t)4); ++i) { cout << hex << setfill('0') << setw(2) << static_cast<unsigned>(static_cast<unsigned char>(m_data[i])) << " "; } cout << endl; } };
int main() {
SecureBuffer *s1 = new SecureBuffer(12); SecureBuffer *s2 = new SecureBuffer(*s1); s1->printData("拷贝之前s1的值"); memset(s2->m_data, 0x11, s2->m_size); s1->printData("拷贝之后s1的值"); return 0; }
|
2.2 悬垂指针
未定义行为 :
- 访问已释放的内存会导致未定义行为,程序可能崩溃或输出不可预测的结果。
潜在的安全漏洞 :
- 如果释放的内存被重新分配给其他对象,攻击者可能利用悬垂指针访问敏感数据。
调试困难 :
- 悬垂指针的问题通常难以复现和调试,尤其是在大型项目中。
使用new 创建的 SecureBuffer 对象之后,显式调用析构函数销毁对象,但不置空指针,并继续通过该指针访问其成员函数,观察并输出悬垂指针带来的后果。
就是销毁对象之后,之前指向对象的指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| #include <iostream> #include <cstring> #include <iomanip> using namespace std;
class SecureBuffer { public: char *m_data; size_t m_size;
SecureBuffer(size_t size) : m_size(size) { m_data = new char[m_size]; memset(m_data, 0xCC, m_size); cout << "Constructor: " << m_size << endl; }
~SecureBuffer() { cout << "Destructor: " << endl; if (m_data) { memset(m_data, 0x00, m_size); delete[] m_data; } }
void printData(const string &step) { cout << step << endl; cout << "Content: "; for (size_t i = 0; i < min(m_size, (size_t)4); ++i) { cout << hex << setfill('0') << setw(2) << static_cast<unsigned>(static_cast<unsigned char>(m_data[i])) << " "; } cout << endl; } };
int main() { for (int i = 0; i < NUM_OBJECTS; ++i) { new SecureBuffer(1024); } cout << "警告:程序未释放 " << NUM_OBJECTS << " 个 SecureBuffer 对象的内存!" << endl; cout << "这将导致大约 " << (NUM_OBJECTS * 1024) / (1024 * 1024) << " MB 的内存泄漏!" << endl; return 0; }
|
2.3 析构函数的调用
动态分配的对象不会自动调用析构函数 : 如果你使用 new
动态创建对象(如 new SecureBuffer(1024)
),这些对象的生命周期是由你手动管理的,它们不会在作用域结束时自动调用析构函数。
只有栈上对象会在作用域结束时自动调用析构函数 : 如果你直接创建对象(如 SecureBuffer buffer(1024);
),这些对象是分配在栈上的,当它们离开作用域时,析构函数会自动调用
3. 相关解决机制
3.1 智能指针
C++中的智能指针是用于管理动态内存的类模板,通过RAII(资源获取即初始化)机制自动管理资源的生命周期,避免内存泄漏和悬空指针问题。
- std::unique_ptr
一个对象进行引用,只属于一个对象。其实智能指针就是一个类,只不过我们拿到类之后。进行对象构造。 不能被拷贝引用。
1 2 3 4
| #include <memory> std::unique_ptr<int> ptr1 = std::make_unique<int>(42); std::unique_ptr<int> ptr2 = std::move(ptr1);
|
但是可以通过移动语义去进行放入vector
1 2 3 4 5 6 7 8 9
| auto ptr = std::make_unique<int>(42); std::vector<std::unique_ptr<int>> vec; vec.push_back(std::move(ptr));
if (!ptr) { std::cout << "ptr 已经为空" << std::endl; }
|
想象你在管理家具:
std::unique_ptr
:
- 家具只能由一个人拥有(独占所有权)。
- 如果你想把家具给别人,必须完全转让所有权(
std::move
)。
- 你不能同时拥有两份相同的家具。
std::shared_ptr
:
- 家具可以被多人共享(引用计数)。
- 每个人都有一份家具的“钥匙”,只有当所有人都归还钥匙时,家具才会被销毁。
- std::shared_ptr
其实就是这个指针能被多个对象引用了。
1 2 3 4
| auto ptr1 = std::make_shared<int>(100); { auto ptr2 = ptr1; }
|
- std::weak_ptr
多个对象引用但是不计数,做一个临时访问。
1 2 3 4 5
| auto shared = std::make_shared<int>(200); std::weak_ptr<int> weak = shared; if (auto temp = weak.lock()) { }
|
3.2 模板函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include <iostream> #include <string>
template <typename T> void print(const T& value) { std::cout << "Value: " << value << std::endl; }
int main() { int i = 42; double d = 3.14; std::string s = "Hello, Template!";
print(i); print(d); print(s);
return 0; }
|
3.3 引用和指针
为了更好地理解 T &ptr
的作用,我们需要明确引用和指针的区别:
特性 |
引用 (T& ) |
指针 (T* ) |
是否可以为空 |
不可以为空(必须绑定到有效变量) |
可以为空 |
是否需要解引用 |
不需要解引用 |
需要通过* 解引用 |
是否可重新赋值 |
一旦绑定,不能重新绑定到其他变量 |
可以随时指向不同的地址 |
是否占用额外内存 |
通常不占用额外内存(编译器优化) |
占用内存(存储地址) |
1 2 3 4 5 6 7 8 9 10 11 12 13
| if (ptr) { memset(ptr, 0x00, sizeof(T)); delete[] ptr; ptr = nullptr; }
|
3.4 移动构造函数
我现在发现,其实移动构造函数就是把浅拷贝的构造函数加上把那个赋值的对象的属性放置为空。
1 2 3 4 5 6 7 8 9 10 11 12 13
| SecureBuffer(SecureBuffer &&other) noexcept : m_data(other.m_data), m_size(other.m_size)
{
other.m_data = nullptr;
other.m_size = 0;
cout << "移动构造函数被调用" << endl;
}
|
- noexcept
不抛出异常处理,因为std:move std:vector 进行处理时候如果抛出异常之后,我们会造成移动拷贝失败,会退回到拷贝操作。影响效率
- SecureBuffer &&other
&&
是 C++11 引入的一个特性,表示 右值引用(Rvalue Reference) 。它专门用于绑定到临时对象或即将被销毁的对象(即“右值”)。
- 左值必须转换成右值
因为不转换那么c++ 默认是拷贝构造函数调用
1 2
| SecureBuffer buf1(100); SecureBuffer buf2 = std::move(buf1);
|
从这些分析中我们也可以看到,我们的移动赋值操作其实跟浅拷贝操作是一样的。但是移动拷贝是移动资源。
3.5 移动赋值运算符
如果说深拷贝的话,其实我们是没有为指针提前赋值的,直接把要拷贝的对象放在构造函数参数,所以不用释放,但是赋值运算符是提前有一个对象,所以要先把他释放再深入拷贝
1 2 3 4 5 6 7 8 9
| SecureBuffer& operator=(const SecureBuffer& other) { if (this != &other) { buffer = new char[other.size]; size = other.size; memcpy(buffer, other.buffer, size); } return *this; }
|
其实这个移动赋值运算符号,跟深拷贝很像,但是触发还是有区别
1 2 3
| SecureBuffer buf1(100); SecureBuffer buf2(50); buf2 = std::move(buf1);
|
必须是已经存在的对象,并且我我们发现必须右边是右值对象,如果不是那么会调用拷贝运算符
3.6 容器
容器 是一种用于存储和管理多个元素的数据结构
1 2 3 4 5 6 7 8
| #include <vector> std::vector<int> vec = {1, 2, 3};
std::vector<int> vec; vec.push_back(1);
int x = vec[0];
|
其实我感觉跟python没啥区别 但就是更快