Con trỏ treo (Dangling Pointer)
Vấn đề
Phần tiêu đề “Vấn đề”Một con trỏ treo (dangling pointer) là con trỏ vẫn giữ địa chỉ của một vùng nhớ đã bị giải phóng hoặc không còn hợp lệ. Dùng nó tiếp tục là hành vi không xác định (undefined behavior).
Trường hợp 1: dùng con trỏ sau khi delete
Phần tiêu đề “Trường hợp 1: dùng con trỏ sau khi delete”int* ptr = new int(42);delete ptr;
std::cout << *ptr << std::endl; // NGUY HIỂM! ptr đang "treo" - vùng nhớ đã bị giải phóngTrường hợp 2: trả về con trỏ đến biến cục bộ
Phần tiêu đề “Trường hợp 2: trả về con trỏ đến biến cục bộ”int* create_number() { int local_value = 42; return &local_value; // NGUY HIỂM! local_value bị hủy khi hàm kết thúc}
int main() { int* ptr = create_number(); std::cout << *ptr << std::endl; // Undefined behavior - biến gốc không còn tồn tại return 0;}Vì sao nguy hiểm?
Phần tiêu đề “Vì sao nguy hiểm?”Vùng nhớ mà con trỏ treo trỏ đến có thể:
- Vẫn còn dữ liệu cũ (chương trình “vô tình chạy đúng” một cách may mắn)
- Đã bị hệ thống cấp phát cho mục đích khác - đọc/ghi vào đó có thể làm hỏng dữ liệu không liên quan
- Gây crash ngay lập tức (nếu vùng nhớ đã được trả lại hệ điều hành)
Tính chất “có thể chạy đúng một cách ngẫu nhiên” khiến lỗi này đặc biệt khó phát hiện khi test.
Giải pháp
Phần tiêu đề “Giải pháp”int* ptr = new int(42);delete ptr;ptr = nullptr; // Luôn gán nullptr ngay sau khi delete
if (ptr != nullptr) { // Kiểm tra trước khi dùng std::cout << *ptr << std::endl;}Với trường hợp trả về con trỏ đến biến cục bộ, giải pháp là dùng cấp phát động (nếu thực sự cần con trỏ sống lâu hơn hàm) hoặc trả về theo giá trị:
int create_number() { // Trả về theo GIÁ TRỊ, không phải con trỏ int local_value = 42; return local_value; // An toàn - trả về một bản sao}Tóm tắt
Phần tiêu đề “Tóm tắt”Con trỏ treo: con trỏ trỏ đến vùng nhớ đã bị giải phóng hoặc không còn hợp lệ.
- Luôn gán
nullptrsau khidelete- Không bao giờ trả về địa chỉ của biến cục bộ (biến trên stack)
- Ưu tiên smart pointer để tránh loại lỗi này gần như hoàn toàn