Cấp phát bộ nhớ động với new/delete
Đến giờ, mọi biến bạn khai báo đều được cấp phát tự động và giải phóng khi ra khỏi phạm vi (scope) - đây gọi là bộ nhớ stack. Nhưng đôi khi bạn cần bộ nhớ tồn tại lâu hơn, hoặc kích thước chỉ biết được lúc chạy chương trình - đó là lúc dùng bộ nhớ heap (cấp phát động).
Stack vs Heap
Phần tiêu đề “Stack vs Heap”| Stack | Heap | |
|---|---|---|
| Cấp phát | Tự động, khi khai báo biến | Thủ công, bằng new |
| Giải phóng | Tự động, khi ra khỏi scope | Thủ công, bằng delete |
| Tốc độ | Rất nhanh | Chậm hơn stack |
| Kích thước | Giới hạn (thường vài MB) | Lớn hơn nhiều (giới hạn bởi RAM) |
| Thời gian sống | Kết thúc khi hàm/khối lệnh kết thúc | Tồn tại đến khi bị delete |
Cấp phát một biến đơn với new
Phần tiêu đề “Cấp phát một biến đơn với new”#include <iostream>
int main() { int* ptr = new int; // Cấp phát một vùng nhớ int trên heap *ptr = 42; // Gán giá trị
std::cout << *ptr << std::endl; // 42
delete ptr; // BẮT BUỘC: giải phóng vùng nhớ khi không dùng nữa ptr = nullptr; // Thói quen tốt: tránh dangling pointer sau khi delete
return 0;}Có thể khởi tạo giá trị ngay khi cấp phát:
int* ptr = new int(42); // Cấp phát và khởi tạo giá trị 42 cùng lúcRò rỉ bộ nhớ (Memory Leak)
Phần tiêu đề “Rò rỉ bộ nhớ (Memory Leak)”Nếu bạn new mà quên delete, vùng nhớ đó sẽ không bao giờ được giải phóng cho đến khi chương trình kết thúc - đây gọi là rò rỉ bộ nhớ (memory leak):
void leaky_function() { int* ptr = new int(42); // Quên delete ptr! Vùng nhớ này bị "mất dấu" mãi mãi khi hàm kết thúc} // ptr (biến con trỏ) bị hủy, nhưng vùng nhớ nó trỏ đến VẪN CÒN bị chiếm dụngNếu hàm này được gọi hàng triệu lần, chương trình sẽ dần dần chiếm hết bộ nhớ RAM và có thể bị hệ điều hành buộc dừng (crash). Mọi lần new phải có đúng một lần delete tương ứng.
Cấp phát mảng động với new[]
Phần tiêu đề “Cấp phát mảng động với new[]”Khi cần cấp phát một mảng có kích thước chỉ biết được lúc chạy chương trình:
#include <iostream>
int main() { int size; std::cout << "Nhap so luong phan tu: "; std::cin >> size;
int* arr = new int[size]; // Cấp phát mảng động với kích thước "size"
for (int i = 0; i < size; i++) { arr[i] = i * 10; }
for (int i = 0; i < size; i++) { std::cout << arr[i] << " "; }
delete[] arr; // Dùng delete[] (có ngoặc vuông) cho mảng, KHÔNG dùng delete thường
return 0;}⚠️ Rất quan trọng: mảng cấp phát bằng new[] phải được giải phóng bằng delete[], không phải delete. Dùng sai sẽ gây ra hành vi không xác định (undefined behavior).
Giải phóng hai lần (Double Free)
Phần tiêu đề “Giải phóng hai lần (Double Free)”Gọi delete hai lần trên cùng một con trỏ cũng là lỗi nghiêm trọng:
int* ptr = new int(42);delete ptr;// delete ptr; // NGUY HIỂM! Giải phóng vùng nhớ đã được giải phóng trước đóGán nullptr cho con trỏ ngay sau khi delete là một thói quen tốt để tránh lỗi này, vì delete nullptr; là an toàn (không làm gì cả).
Khi nào thực sự cần new/delete?
Phần tiêu đề “Khi nào thực sự cần new/delete?”Trong C++ hiện đại, việc dùng trực tiếp new/delete ngày càng ít gặp, vì:
std::vectorđã tự quản lý mảng động cho bạn- Smart pointers (
std::unique_ptr,std::shared_ptr- sẽ học ở bài riêng) tự động gọideleteđúng lúc, giúp tránh rò rỉ bộ nhớ
Tuy nhiên, hiểu rõ new/delete là nền tảng bắt buộc để hiểu cách các công cụ hiện đại đó hoạt động bên dưới.
Tổng kết
Phần tiêu đề “Tổng kết”newcấp phát bộ nhớ trên heap, tồn tại cho đến khi bịdeletemột cách tường minh- Mỗi
newphải có đúng mộtdeletetương ứng - quêndeletegây rò rỉ bộ nhớ (memory leak) - Mảng cấp phát bằng
new[]phải giải phóng bằngdelete[] - Gán
nullptrsau khideleteđể tránh lỗi giải phóng hai lần (double free)