Bài 16: Quản lý bộ nhớ động (Dynamic Memory Management)
📖 Giới thiệu
Quản lý bộ nhớ động (Dynamic Memory Management) là khả năng cấp phát và giải phóng bộ nhớ trong thời gian chạy chương trình (runtime). Đây là một tính năng quan trọng và mạnh mẽ của C++.
Khác với static memory (bộ nhớ tĩnh) được cấp phát khi biến được khai báo, dynamic memory cho phép chúng ta:
- Cấp phát bộ nhớ khi cần: Không cần biết trước kích thước
- Giải phóng khi không dùng: Tiết kiệm tài nguyên hệ thống
- Tạo cấu trúc linh hoạt: Linked list, tree, graph
- Xử lý dữ liệu lớn: Files, databases, big data
Tưởng tượng bộ nhớ như một khách sạn:
- Static memory: Phòng được đặt trước, cố định
- Dynamic memory: Thuê phòng khi cần, trả khi xong
🔧 Cú pháp
Cấp phát bộ nhớ với new
cpp
// Cấp phát cho một biến
int* ptr = new int; // Cấp phát 1 int
int* ptr2 = new int(42); // Cấp phát và khởi tạo giá trị 42
double* dPtr = new double(3.14);
// Cấp phát cho mảng
int* arr = new int[10]; // Mảng 10 phần tử
double* darr = new double[100]; // Mảng 100 double
// Cấp phát và khởi tạo mảng (C++11)
int* arr2 = new int[5]{1, 2, 3, 4, 5};Giải phóng bộ nhớ với delete
cpp
// Giải phóng biến đơn
delete ptr;
delete ptr2;
delete dPtr;
// Giải phóng mảng
delete[] arr;
delete[] darr;
delete[] arr2;
// Đặt pointer về nullptr sau khi delete
ptr = nullptr;
arr = nullptr;Kiểm tra cấp phát thành công
cpp
int* ptr = new(nothrow) int[1000000];
if (ptr == nullptr) {
cout << "Lỗi: Không đủ bộ nhớ!" << endl;
return -1;
}
// Sử dụng ptr...
delete[] ptr;
ptr = nullptr;🔬 Phân tích & Giải thích chi tiết
1. Stack vs Heap Memory
cpp
#include <iostream>
using namespace std;
void demonstrateMemoryTypes() {
// STACK MEMORY (tự động quản lý)
int stackVar = 100; // Biến cục bộ trên stack
int stackArray[5] = {1,2,3,4,5}; // Mảng trên stack
cout << "=== STACK MEMORY ===" << endl;
cout << "Stack variable: " << stackVar << endl;
cout << "Stack array: ";
for (int i = 0; i < 5; i++) {
cout << stackArray[i] << " ";
}
cout << endl;
// HEAP MEMORY (thủ công quản lý)
int* heapVar = new int(200); // Biến trên heap
int* heapArray = new int[5]{6,7,8,9,10}; // Mảng trên heap
cout << "\n=== HEAP MEMORY ===" << endl;
cout << "Heap variable: " << *heapVar << endl;
cout << "Heap array: ";
for (int i = 0; i < 5; i++) {
cout << heapArray[i] << " ";
}
cout << endl;
// So sánh địa chỉ
cout << "\n=== MEMORY ADDRESSES ===" << endl;
cout << "Stack variable address: " << &stackVar << endl;
cout << "Heap variable address: " << heapVar << endl;
cout << "Stack array address: " << stackArray << endl;
cout << "Heap array address: " << heapArray << endl;
// Giải phóng heap memory
delete heapVar;
delete[] heapArray;
heapVar = nullptr;
heapArray = nullptr;
// Stack memory tự động giải phóng khi thoát hàm
}
int main() {
demonstrateMemoryTypes();
return 0;
}2. Quản lý mảng động
cpp
#include <iostream>
#include <string>
using namespace std;
class DynamicArray {
private:
int* data;
int size;
int capacity;
public:
// Constructor
DynamicArray(int initialCapacity = 10) {
capacity = initialCapacity;
size = 0;
data = new int[capacity];
cout << "✅ Đã cấp phát " << capacity << " phần tử" << endl;
}
// Destructor
~DynamicArray() {
delete[] data;
cout << "🗑️ Đã giải phóng bộ nhớ" << endl;
}
// Thêm phần tử
void push_back(int value) {
if (size >= capacity) {
resize();
}
data[size++] = value;
}
// Mở rộng mảng
void resize() {
int newCapacity = capacity * 2;
int* newData = new int[newCapacity];
// Copy dữ liệu cũ
for (int i = 0; i < size; i++) {
newData[i] = data[i];
}
// Giải phóng bộ nhớ cũ
delete[] data;
// Cập nhật
data = newData;
capacity = newCapacity;
cout << "📈 Đã mở rộng mảng: " << capacity << " phần tử" << endl;
}
// Truy cập phần tử
int& operator[](int index) {
if (index < 0 || index >= size) {
throw out_of_range("Index ngoài phạm vi!");
}
return data[index];
}
// Getter
int getSize() const { return size; }
int getCapacity() const { return capacity; }
// In mảng
void print() const {
cout << "Mảng [" << size << "/" << capacity << "]: ";
for (int i = 0; i < size; i++) {
cout << data[i] << " ";
}
cout << endl;
}
};
int main() {
cout << "=== DEMO DYNAMIC ARRAY ===" << endl;
DynamicArray arr(3); // Bắt đầu với 3 phần tử
// Thêm các phần tử
for (int i = 1; i <= 10; i++) {
arr.push_back(i * 10);
arr.print();
}
// Truy cập phần tử
cout << "\nPhần tử thứ 5: " << arr[4] << endl;
return 0;
// Destructor tự động được gọi
}3. Memory Leaks và cách tránh
cpp
#include <iostream>
#include <vector>
using namespace std;
// ❌ BAD: Memory leak
void badFunction() {
int* ptr = new int[1000];
// Quên delete[] ptr;
// Memory leak xảy ra!
}
// ✅ GOOD: Proper cleanup
void goodFunction() {
int* ptr = new int[1000];
// Làm việc với ptr...
delete[] ptr;
ptr = nullptr;
}
// ❌ BAD: Exception safety issue
void riskyFunction() {
int* ptr = new int[1000];
// Nếu có exception ở đây...
if (true) {
throw runtime_error("Lỗi!");
}
delete[] ptr; // Không bao giờ được gọi!
}
// ✅ GOOD: RAII pattern
class SafeArray {
private:
int* data;
int size;
public:
SafeArray(int size) : size(size) {
data = new int[size];
}
~SafeArray() {
delete[] data;
}
// Disable copy (tránh double delete)
SafeArray(const SafeArray&) = delete;
SafeArray& operator=(const SafeArray&) = delete;
int& operator[](int index) {
return data[index];
}
int getSize() const { return size; }
};
// ✅ BETTER: Sử dụng STL containers
void modernFunction() {
vector<int> vec(1000); // Tự động quản lý memory
// Không cần delete, vector tự động dọn dẹp
}
int main() {
cout << "=== MEMORY MANAGEMENT EXAMPLES ===" << endl;
// Good practices
goodFunction();
{
SafeArray arr(100);
arr[0] = 42;
cout << "Safe array first element: " << arr[0] << endl;
// Destructor tự động gọi khi thoát scope
}
modernFunction();
return 0;
}💻 Ví dụ minh họa
Chương trình quản lý danh bạ động
cpp
#include <iostream>
#include <string>
#include <cstring>
using namespace std;
struct Contact {
char* name;
char* phone;
char* email;
// Constructor
Contact(const char* n = "", const char* p = "", const char* e = "") {
// Cấp phát và copy string
name = new char[strlen(n) + 1];
strcpy(name, n);
phone = new char[strlen(p) + 1];
strcpy(phone, p);
email = new char[strlen(e) + 1];
strcpy(email, e);
}
// Copy constructor
Contact(const Contact& other) {
name = new char[strlen(other.name) + 1];
strcpy(name, other.name);
phone = new char[strlen(other.phone) + 1];
strcpy(phone, other.phone);
email = new char[strlen(other.email) + 1];
strcpy(email, other.email);
}
// Destructor
~Contact() {
delete[] name;
delete[] phone;
delete[] email;
}
// Assignment operator
Contact& operator=(const Contact& other) {
if (this == &other) return *this;
// Giải phóng memory cũ
delete[] name;
delete[] phone;
delete[] email;
// Cấp phát mới và copy
name = new char[strlen(other.name) + 1];
strcpy(name, other.name);
phone = new char[strlen(other.phone) + 1];
strcpy(phone, other.phone);
email = new char[strlen(other.email) + 1];
strcpy(email, other.email);
return *this;
}
void display() const {
cout << "📞 " << name << " - " << phone << " - " << email << endl;
}
};
class PhoneBook {
private:
Contact** contacts; // Mảng con trỏ đến Contact
int size;
int capacity;
void resize() {
int newCapacity = capacity * 2;
Contact** newContacts = new Contact*[newCapacity];
// Copy pointers
for (int i = 0; i < size; i++) {
newContacts[i] = contacts[i];
}
delete[] contacts;
contacts = newContacts;
capacity = newCapacity;
cout << "📈 Đã mở rộng danh bạ: " << capacity << " liên hệ" << endl;
}
public:
PhoneBook(int initialCapacity = 5) {
capacity = initialCapacity;
size = 0;
contacts = new Contact*[capacity];
}
~PhoneBook() {
// Giải phóng từng contact
for (int i = 0; i < size; i++) {
delete contacts[i];
}
// Giải phóng mảng pointers
delete[] contacts;
}
void addContact(const char* name, const char* phone, const char* email) {
if (size >= capacity) {
resize();
}
contacts[size] = new Contact(name, phone, email);
size++;
cout << "✅ Đã thêm liên hệ: " << name << endl;
}
void removeContact(int index) {
if (index < 0 || index >= size) {
cout << "❌ Index không hợp lệ!" << endl;
return;
}
cout << "🗑️ Xóa: " << contacts[index]->name << endl;
// Giải phóng contact
delete contacts[index];
// Dịch chuyển các phần tử
for (int i = index; i < size - 1; i++) {
contacts[i] = contacts[i + 1];
}
size--;
}
void displayAll() const {
if (size == 0) {
cout << "📱 Danh bạ trống!" << endl;
return;
}
cout << "\n📋 DANH BẠ (" << size << "/" << capacity << "):" << endl;
for (int i = 0; i < size; i++) {
cout << i + 1 << ". ";
contacts[i]->display();
}
}
int findContact(const char* name) const {
for (int i = 0; i < size; i++) {
if (strcmp(contacts[i]->name, name) == 0) {
return i;
}
}
return -1;
}
void searchContact(const char* keyword) const {
cout << "\n🔍 KẾT QUẢ TÌM KIẾM: '" << keyword << "'" << endl;
bool found = false;
for (int i = 0; i < size; i++) {
if (strstr(contacts[i]->name, keyword) != nullptr ||
strstr(contacts[i]->phone, keyword) != nullptr ||
strstr(contacts[i]->email, keyword) != nullptr) {
cout << i + 1 << ". ";
contacts[i]->display();
found = true;
}
}
if (!found) {
cout << "Không tìm thấy kết quả nào!" << endl;
}
}
int getSize() const { return size; }
int getCapacity() const { return capacity; }
};
void showMenu() {
cout << "\n📱 QUẢN LÝ DANH BẠ" << endl;
cout << "1. Thêm liên hệ" << endl;
cout << "2. Hiển thị tất cả" << endl;
cout << "3. Tìm kiếm" << endl;
cout << "4. Xóa liên hệ" << endl;
cout << "5. Thống kê" << endl;
cout << "0. Thoát" << endl;
cout << "Chọn chức năng (0-5): ";
}
int main() {
PhoneBook phoneBook(3); // Bắt đầu với capacity = 3
int choice;
// Thêm một số liên hệ mẫu
phoneBook.addContact("Nguyễn Văn An", "0123456789", "an@email.com");
phoneBook.addContact("Trần Thị Bình", "0987654321", "binh@email.com");
do {
showMenu();
cin >> choice;
cin.ignore(); // Bỏ qua ký tự newline
switch (choice) {
case 1: {
char name[100], phone[20], email[100];
cout << "Nhập tên: ";
cin.getline(name, 100);
cout << "Nhập số điện thoại: ";
cin.getline(phone, 20);
cout << "Nhập email: ";
cin.getline(email, 100);
phoneBook.addContact(name, phone, email);
break;
}
case 2:
phoneBook.displayAll();
break;
case 3: {
char keyword[100];
cout << "Nhập từ khóa tìm kiếm: ";
cin.getline(keyword, 100);
phoneBook.searchContact(keyword);
break;
}
case 4: {
phoneBook.displayAll();
if (phoneBook.getSize() > 0) {
int index;
cout << "Nhập số thứ tự cần xóa: ";
cin >> index;
phoneBook.removeContact(index - 1);
}
break;
}
case 5:
cout << "\n📊 THỐNG KÊ:" << endl;
cout << "Tổng số liên hệ: " << phoneBook.getSize() << endl;
cout << "Dung lượng hiện tại: " << phoneBook.getCapacity() << endl;
cout << "Tỷ lệ sử dụng: " <<
(phoneBook.getSize() * 100.0 / phoneBook.getCapacity()) << "%" << endl;
break;
case 0:
cout << "👋 Cảm ơn bạn đã sử dụng!" << endl;
break;
default:
cout << "❌ Lựa chọn không hợp lệ!" << endl;
}
} while (choice != 0);
return 0;
// Destructor tự động giải phóng tất cả memory
}🏋️ Thực hành
Bài tập 1: Ma trận động
cpp
#include <iostream>
using namespace std;
class DynamicMatrix {
private:
int** matrix;
int rows, cols;
public:
DynamicMatrix(int r, int c) : rows(r), cols(c) {
// Cấp phát mảng con trỏ
matrix = new int*[rows];
// Cấp phát từng hàng
for (int i = 0; i < rows; i++) {
matrix[i] = new int[cols];
}
}
~DynamicMatrix() {
// Giải phóng từng hàng
for (int i = 0; i < rows; i++) {
delete[] matrix[i];
}
// Giải phóng mảng con trỏ
delete[] matrix;
}
void input() {
cout << "Nhập ma trận " << rows << "x" << cols << ":" << endl;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
cout << "matrix[" << i << "][" << j << "] = ";
cin >> matrix[i][j];
}
}
}
void display() const {
cout << "Ma trận:" << endl;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
cout << matrix[i][j] << "\t";
}
cout << endl;
}
}
// Operator overloading
int* operator[](int row) {
return matrix[row];
}
};
int main() {
int rows, cols;
cout << "Nhập số hàng và cột: ";
cin >> rows >> cols;
DynamicMatrix mat(rows, cols);
mat.input();
mat.display();
return 0;
}Bài tập 2: Linked List đơn giản
cpp
#include <iostream>
using namespace std;
struct Node {
int data;
Node* next;
Node(int value) : data(value), next(nullptr) {}
};
class LinkedList {
private:
Node* head;
int size;
public:
LinkedList() : head(nullptr), size(0) {}
~LinkedList() {
clear();
}
void push_front(int value) {
Node* newNode = new Node(value);
newNode->next = head;
head = newNode;
size++;
}
void push_back(int value) {
Node* newNode = new Node(value);
if (head == nullptr) {
head = newNode;
} else {
Node* current = head;
while (current->next != nullptr) {
current = current->next;
}
current->next = newNode;
}
size++;
}
void display() const {
Node* current = head;
cout << "List: ";
while (current != nullptr) {
cout << current->data << " -> ";
current = current->next;
}
cout << "NULL" << endl;
}
void clear() {
while (head != nullptr) {
Node* temp = head;
head = head->next;
delete temp;
}
size = 0;
}
int getSize() const { return size; }
};
int main() {
LinkedList list;
list.push_back(10);
list.push_back(20);
list.push_front(5);
list.display();
cout << "Size: " << list.getSize() << endl;
return 0;
}📋 Tóm tắt
Kiến thức đã học:
Dynamic Memory Allocation:
new: Cấp phát single object hoặc arraydelete/delete[]: Giải phóng memorynew(nothrow): Cấp phát an toàn
Memory Management Best Practices:
- Luôn
deletesau khinew - Đặt pointer về
nullptrsaudelete - Sử dụng RAII pattern
- Tránh memory leaks
- Luôn
Common Patterns:
- Dynamic arrays with resizing
- Linked data structures
- Resource management classes
- Exception safety
Lỗi thường gặp:
- Memory leaks: Quên
delete - Double delete:
deletehai lần - Dangling pointers: Sử dụng pointer sau
delete - Wrong delete:
deletethay vìdelete[]
Chuẩn bị cho bài tiếp theo:
- Smart pointers (unique_ptr, shared_ptr)
- RAII và resource management
- Move semantics
- Modern C++ memory management
"Dynamic memory giống như thuê nhà - thuê khi cần, trả khi xong, và nhớ trả đúng chỗ!"
Chuyển sang: Con trỏ nâng cao để học về smart pointers và memory management hiện đại!