Skip to content

🏭 Memory Model: Stack vs Heap

Trong C++, bạn là người quản lý kho hàng. Hiểu cách bộ nhớ hoạt động là chìa khóa để viết code hiệu quả và tránh bugs nguy hiểm.

Ẩn dụ "Kho hàng" (The Warehousing Metaphor)

Hãy tưởng tượng bộ nhớ của chương trình như một nhà kho lớn với hai khu vực:

┌─────────────────────────────────────────────────────────────────┐
│                        NHÀ KHO BỘ NHỚ                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  📦 KỆ HÀNG (STACK)              📦📦📦 KHO LỚN (HEAP)         │
│  ──────────────────              ────────────────────           │
│  • Nhỏ gọn, nhanh               • Rộng lớn, linh hoạt           │
│  • Xếp chồng lên nhau           • Đặt ở bất kỳ đâu              │
│  • Tự động dọn dẹp              • Phải TỰ dọn dẹp!              │
│  • Kích thước cố định           • Kích thước linh hoạt          │
│                                                                 │
│  [Biến local]                   [new/malloc allocations]        │
│  [Function params]              [Dynamic arrays]                │
│  [Return addresses]             [Large objects]                 │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Stack: Kệ hàng — Nhanh và Tự động

Stack hoạt động theo nguyên tắc LIFO (Last In, First Out) — như chồng đĩa:

cpp
#include <iostream>

void functionB() {
    int y = 20;  // y được đẩy lên stack
    std::cout << "y tại địa chỉ: " << &y << std::endl;
}  // y tự động bị pop khỏi stack

void functionA() {
    int x = 10;  // x được đẩy lên stack
    functionB(); // Gọi functionB, stack frame mới được tạo
    std::cout << "x tại địa chỉ: " << &x << std::endl;
}  // x tự động bị pop khỏi stack

int main() {
    functionA();
    return 0;
}

Visualize Stack:

Khi functionB() đang chạy:
┌───────────────────┐
│  y = 20           │ ← Top of stack (functionB's frame)
├───────────────────┤
│  return address   │
├───────────────────┤
│  x = 10           │ ← functionA's frame
├───────────────────┤
│  return address   │
├───────────────────┤
│  main's locals    │ ← main's frame
└───────────────────┘

   Stack grows DOWN

📌 Stack Characteristics

Đặc điểmGiá trị
Tốc độ⚡⚡⚡ Rất nhanh (chỉ di chuyển stack pointer)
Kích thước1-8 MB (tùy OS, có thể điều chỉnh)
Quản lýTự động (compiler quản lý)
LifetimeTồn tại trong scope của function

Heap: Kho lớn — Linh hoạt nhưng Manual

Heap cho phép bạn cấp phát bộ nhớ động — kích thước được quyết định lúc runtime:

cpp
#include <iostream>

int main() {
    // Cấp phát trên HEAP bằng "new"
    int* ptr = new int(42);
    
    std::cout << "Giá trị: " << *ptr << std::endl;
    std::cout << "Địa chỉ trên heap: " << ptr << std::endl;
    
    // ⚠️ BẮT BUỘC: Giải phóng bộ nhớ!
    delete ptr;
    ptr = nullptr;  // Good practice: tránh dangling pointer
    
    return 0;
}

Array động trên Heap:

cpp
#include <iostream>

int main() {
    int size;
    std::cout << "Nhập kích thước mảng: ";
    std::cin >> size;
    
    // Cấp phát array động trên heap
    int* arr = new int[size];
    
    // Sử dụng array
    for (int i = 0; i < size; ++i) {
        arr[i] = i * 10;
    }
    
    // In array
    for (int i = 0; i < size; ++i) {
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;
    
    // ⚠️ BẮT BUỘC: Giải phóng array
    delete[] arr;  // Lưu ý: delete[] cho arrays!
    arr = nullptr;
    
    return 0;
}

🔥 Memory Leak — Lỗi kinh điển

cpp
void leakyFunction() {
    int* data = new int[1000];
    // ... làm gì đó
    
    // ❌ QUÊN delete[] data!
    // Bộ nhớ bị "mất" — không ai có thể truy cập nữa
    // Nhưng nó vẫn chiếm RAM cho đến khi chương trình kết thúc
}

Hậu quả: Chạy vòng lặp 1 triệu lần → Mất ~4GB RAM!


Memory Layout hoàn chỉnh

Một chương trình C++ có layout bộ nhớ như sau:

┌─────────────────────────────────────────┐  Địa chỉ cao
│                 STACK                   │  ↓ Grows down
│    (local variables, function calls)   │
├─────────────────────────────────────────┤
│                    ↓                    │
│              (unused space)             │
│                    ↑                    │
├─────────────────────────────────────────┤
│                  HEAP                   │  ↑ Grows up
│         (dynamic allocations)           │
├─────────────────────────────────────────┤
│                  .BSS                   │
│  (uninitialized global/static vars)    │
│  (initialized to 0 at startup)         │
├─────────────────────────────────────────┤
│                 .DATA                   │
│   (initialized global/static vars)     │
├─────────────────────────────────────────┤
│                 .TEXT                   │  Địa chỉ thấp
│        (compiled machine code)          │
└─────────────────────────────────────────┘

Code minh họa các vùng nhớ:

cpp
#include <iostream>

// .DATA segment (initialized)
int globalInit = 100;

// .BSS segment (uninitialized → auto 0)
int globalUnInit;

int main() {
    // STACK
    int localVar = 50;
    
    // HEAP
    int* heapVar = new int(200);
    
    std::cout << "=== Memory Layout Demo ===" << std::endl;
    std::cout << "globalInit  (.DATA): " << &globalInit << std::endl;
    std::cout << "globalUnInit (.BSS): " << &globalUnInit << std::endl;
    std::cout << "localVar    (STACK): " << &localVar << std::endl;
    std::cout << "heapVar     (HEAP):  " << heapVar << std::endl;
    
    delete heapVar;
    return 0;
}

Output mẫu:

=== Memory Layout Demo ===
globalInit  (.DATA): 0x404018
globalUnInit (.BSS): 0x40401c
localVar    (STACK): 0x7ffd5c3b3a4c
heapVar     (HEAP):  0x55d8e7b68eb0

🎓 Quan sát về địa chỉ

  • Stack (0x7fff...) ở vùng địa chỉ cao
  • Heap (0x55d8...) ở vùng địa chỉ thấp hơn stack
  • Data segments (0x4040...) ở vùng địa chỉ thấp nhất

Stack vs Heap: Khi nào dùng gì?

Tiêu chíStackHeap
Kích thước biết trước?✅ Dùng Stack❌ Dùng Heap
Cần tồn tại sau function return?✅ Dùng Heap
Kích thước lớn (>1MB)?❌ Stack overflow!✅ Dùng Heap
Performance critical?✅ Stack nhanh hơn❌ Heap chậm hơn
Lifetime ngắn?✅ Dùng Stack
cpp
// ✅ Stack — Biết trước, nhỏ, ngắn hạn
void processSmall() {
    int buffer[100];  // 400 bytes on stack — OK
    // ...
}

// ✅ Heap — Lớn hoặc dynamic size
void processLarge() {
    // ❌ Đừng làm này! Stack overflow risk!
    // int hugeArray[10000000];
    
    // ✅ Dùng heap cho large allocations
    int* hugeArray = new int[10000000];
    // ... use it
    delete[] hugeArray;
}

🐛 Spot the Bug: Memory Edition

🐛 Bug Hunt Challenge

Đoạn code sau có bug gì?

cpp
int* createArray() {
    int arr[5] = {1, 2, 3, 4, 5};
    return arr;  // 💥 Bug ở đây!
}

int main() {
    int* result = createArray();
    std::cout << result[0] << std::endl;  // ❓ In ra gì?
    return 0;
}
💡 Gợi ý

arr được lưu ở đâu? Stack hay Heap?

🔍 Giải thích chi tiết

Bug: arr là biến local trên stack. Khi createArray() return, stack frame bị pop → arr không còn hợp lệ!

result trỏ đến vùng nhớ đã bị "thu hồi" → Dangling pointerUndefined Behavior!

Fix 1 — Dynamic allocation:

cpp
int* createArray() {
    int* arr = new int[5]{1, 2, 3, 4, 5};
    return arr;  // ✅ Heap data survives
}
// Caller phải delete[] sau khi dùng xong!

Fix 2 — Modern C++ (std::vector):

cpp
#include <vector>

std::vector<int> createArray() {
    return {1, 2, 3, 4, 5};  // ✅ Move semantics, no leak
}

📚 Tổng kết

ConceptKey Takeaway
StackTự động, nhanh, LIFO, kích thước giới hạn
HeapManual, linh hoạt, phải delete, dễ leak
Memory LeakQuên delete → RAM bị chiếm mãi
Dangling PointerTrỏ đến vùng nhớ đã bị thu hồi
new/deleteC++ way — match new[] với delete[]

➡️ Tiếp theo

Bạn đã hiểu Memory Model. Tiếp theo: Variables & Types — Các kiểu dữ liệu cơ bản và cách chúng được lưu trong bộ nhớ.