Chuyển đến nội dung

Bộ nhớ động: mới, xóa & Mối nguy hiểm của chúng

Tìm hiểu cách phân bổ động hoạt động với tính năng mới và xóa, hiểu sự nguy hiểm của rò rỉ bộ nhớ, trống kép và không sử dụng sau này, đồng thời khám phá lý do C++ hiện đại không chứa dữ liệu mới/xóa thô.

Phân bổ động với tính năng mới và xóa

ngăn xếp nhanh nhưng bị giới hạn về kích thước và phạm vi. Khi bạn cần các đối tượng tồn tại lâu hơn phạm vi tạo của chúng hoặc có kích thước không xác định tại thời điểm biên dịch, bạn phân bổ trên heap (cửa hàng miễn phí).

new T(args)phân bổ bộ nhớ cho mộtT, gọi hàm tạo của nó và trả vềT*.delete pgọi hàm hủy và giải phóng bộ nhớ. Đối với mảng:new T[n]vàdelete[] p. newkhông khớp vớidelete[]hoặcnew[]vớideletelà hành vi không xác định — trình biên dịch sẽ không phát hiện được điều này.

mới, xóa và các biến thể mảng của chúng

Đây là API phân bổ thô. Nghiên cứu nó để bạn nhận ra nó trong mã cũ, nhưng không bao giờ viết mã này bằng mã mới — thay vào đó hãy sử dụng con trỏ thông minh.

new_delete.cpp
#include <iostream>
#include <cstring>

struct Widget {
    int id;
    Widget(int i) : id(i) { std::cout << "Widget " << id << " created\n"; }
    ~Widget() { std::cout << "Widget " << id << " destroyed\n"; }
};

int main() {
    // Single object
    Widget* w = new Widget(1);
    std::cout << "Using widget " << w->id << '\n';
    delete w;  // destructor called, memory freed

    // Array
    Widget* arr = new Widget[3]{{10}, {20}, {30}};
    for (int i = 0; i < 3; ++i)
        std::cout << arr[i].id << ' ';
    std::cout << '\n';
    delete[] arr;  // MUST use delete[] for new[]

    // UNDEFINED BEHAVIOR — don't do these:
    // delete arr;      // wrong: used delete instead of delete[]
    // delete[] w;      // wrong: used delete[] instead of delete
}

Vị trí mới

Vị trí mới xây dựng một đối tượng tại một địa chỉ bộ nhớ cụ thể mà không cần phân bổ. Nó được sử dụng bởi các bộ cấp phát, nhóm bộ nhớ và các vùng chứa nhưstd::vectortrong nội bộ. Bạn phải gọi hàm hủy một cách rõ ràng - KHÔNG được sử dụngdeletetrên các đối tượng có vị trí mới.

placement_new.cpp
#include <iostream>
#include <new>       // required for placement new
#include <cstddef>   // std::byte
#include <memory>    // std::align

struct Sensor {
    int id;
    double reading;
    Sensor(int i, double r) : id(i), reading(r) {
        std::cout << "Sensor " << id << " init\n";
    }
    ~Sensor() { std::cout << "Sensor " << id << " shutdown\n"; }
};

int main() {
    // Pre-allocate raw storage (aligned)
    alignas(Sensor) std::byte buffer[sizeof(Sensor)];

    // Construct in pre-allocated memory
    Sensor* s = new (buffer) Sensor(42, 3.14);
    std::cout << "Reading: " << s->reading << '\n';

    // Must destroy explicitly — do NOT use delete
    s->~Sensor();  // explicit destructor call
    // delete s;    // WRONG — memory was not allocated by new
}

Ba lỗi trí nhớ chết người

new/deletethô khiến bạn gặp phải ba loại lỗi:

1. Rò rỉ bộ nhớ - phân bổ bằngnewnhưng không bao giờ gọidelete. Bộ nhớ không bao giờ được trả lại cho HĐH (cho đến khi thoát khỏi quá trình). Trong một máy chủ hoạt động lâu dài, điều này cuối cùng sẽ tiêu tốn hết RAM có sẵn.

2. Double Free — gọideletetrên cùng một con trỏ hai lần.deletethứ hai làm hỏng cấu trúc dữ liệu nội bộ của bộ cấp phát heap, thường dẫn đến sự cố sau này ở mã không liên quan. Điều này cực kỳ khó gỡ lỗi.

3. Sử dụng sau khi miễn phí — truy cập bộ nhớ saudelete. Bộ nhớ có thể đã được sử dụng lại cho mục đích phân bổ khác, vì vậy bạn có thể đang đọc hoặc ghi dữ liệu của đối tượng khác. Đây là một lỗ hổng bảo mật (lỗi lớp CVE).

Lỗi thường gặp

Hãy xem xét:f(new A(), new B()). Trình biên dịch có thể đánh giá các đối số theo bất kỳ thứ tự nào và có thể xen kẽ chúng:

1. Cấp phát bộ nhớ cho A
2. Cấp phát bộ nhớ cho B
3. Xây dựng A
4. Xây dựng B

Nếu bước 4 không thực hiện được, A sẽ bị rò rỉ - không ai gọideletetrên đó. Ngay cảf(std::shared_ptr(new A()), std::shared_ptr(new B()))cũng gặp vấn đề này trước C++17 (đảm bảo rằng mỗi đối số được đánh giá đầy đủ trước đối số tiếp theo). Cách khắc phục: luôn sử dụngstd::make_uniquehoặcstd::make_shared — chúng thực hiện phân bổ và xây dựng dưới dạng một hoạt động duy nhất, an toàn ngoại lệ.