Giải phóng bộ nhớ hai lần (Double Free)
Vấn đề
Phần tiêu đề “Vấn đề”Gọi delete hai lần trên cùng một con trỏ (mà không cấp phát lại ở giữa) là undefined behavior, thường dẫn đến crash chương trình:
int* ptr = new int(42);
delete ptr; // Giải phóng lần 1 - OKdelete ptr; // Giải phóng lần 2 - NGUY HIỂM! Vùng nhớ này đã được giải phóng trước đóVì sao nguy hiểm?
Phần tiêu đề “Vì sao nguy hiểm?”Sau lần delete đầu tiên, vùng nhớ đó có thể đã được hệ thống cấp phát cho một mục đích hoàn toàn khác. Lần delete thứ hai có thể vô tình giải phóng vùng nhớ đang được dùng bởi một phần khác của chương trình - dẫn đến lỗi khó lường ở một nơi hoàn toàn không liên quan.
Trường hợp thực tế dễ mắc phải: hai con trỏ cùng trỏ một vùng nhớ
Phần tiêu đề “Trường hợp thực tế dễ mắc phải: hai con trỏ cùng trỏ một vùng nhớ”int* a = new int(42);int* b = a; // b và a CÙNG trỏ đến một vùng nhớ (không phải sao chép giá trị được trỏ đến!)
delete a; // Giải phóng vùng nhớdelete b; // DOUBLE FREE! b vẫn đang trỏ đến vùng nhớ mà a vừa giải phóngĐây thực chất là hệ quả của “sao chép nông” đã học ở bài trước - khi hai con trỏ vô tình trỏ chung một địa chỉ, cả hai đều có “trách nhiệm” giải phóng nó, dẫn đến giải phóng hai lần.
Giải pháp 1: gán nullptr ngay sau khi delete
Phần tiêu đề “Giải pháp 1: gán nullptr ngay sau khi delete”int* ptr = new int(42);delete ptr;ptr = nullptr; // Quan trọng!
// delete nullptr; là AN TOÀN - không làm gì cả, không gây lỗidelete ptr; // An toàn vì ptr đã là nullptrĐây là lý do vì sao thói quen “gán nullptr ngay sau delete” luôn được khuyến khích - delete trên một con trỏ null được chuẩn C++ đảm bảo là an toàn tuyệt đối (không làm gì cả).
Giải pháp 2: xác định rõ ai là “chủ sở hữu”
Phần tiêu đề “Giải pháp 2: xác định rõ ai là “chủ sở hữu””int* a = new int(42);int* b = a; // b chỉ là một cách tham chiếu tạm thời, KHÔNG sở hữu vùng nhớ này
delete a; // Chỉ CHỦ SỞ HỮU (a) mới được deletea = nullptr;b = nullptr; // b không delete, chỉ đơn giản không dùng đến nữaGiải pháp tốt nhất: dùng smart pointer
Phần tiêu đề “Giải pháp tốt nhất: dùng smart pointer”std::unique_ptr khiến double free gần như không thể xảy ra, vì nó không cho phép sao chép (chỉ có thể move), nên không thể có hai đối tượng cùng “nghĩ mình sở hữu” một vùng nhớ:
#include <memory>
auto a = std::make_unique<int>(42);// auto b = a; // LỖI BIÊN DỊCH! Không thể sao chép unique_ptr - ngăn chặn double free ngay từ khi biên dịch
auto b = std::move(a); // OK - chuyển quyền sở hữu, giờ chỉ b hợp lệ, a là nullptrTóm tắt
Phần tiêu đề “Tóm tắt”Double free: gọi
deletehai lần trên cùng một vùng nhớ - undefined behavior, thường gây crash hoặc phá hỏng dữ liệu không liên quan.
- Thường xảy ra khi hai con trỏ vô tình trỏ chung một địa chỉ (hệ quả của sao chép nông)
- Luôn gán
nullptrsau khidelete-delete nullptrluôn an toànunique_ptrngăn chặn double free ngay từ khi biên dịch, vì không cho phép sao chép