Skip to content

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:

  1. Dynamic Memory Allocation:

    • new: Cấp phát single object hoặc array
    • delete/delete[]: Giải phóng memory
    • new(nothrow): Cấp phát an toàn
  2. Memory Management Best Practices:

    • Luôn delete sau khi new
    • Đặt pointer về nullptr sau delete
    • Sử dụng RAII pattern
    • Tránh memory leaks
  3. 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: delete hai lần
  • Dangling pointers: Sử dụng pointer sau delete
  • Wrong delete: delete thay 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!

Khóa học C++ miễn phí