Giao diện
🏭 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ểm | Giá trị |
|---|---|
| Tốc độ | ⚡⚡⚡ Rất nhanh (chỉ di chuyển stack pointer) |
| Kích thước | 1-8 MB (tùy OS, có thể điều chỉnh) |
| Quản lý | Tự động (compiler quản lý) |
| Lifetime | Tồ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í | Stack | Heap |
|---|---|---|
| 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 pointer → Undefined 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
| Concept | Key Takeaway |
|---|---|
| Stack | Tự động, nhanh, LIFO, kích thước giới hạn |
| Heap | Manual, linh hoạt, phải delete, dễ leak |
| Memory Leak | Quên delete → RAM bị chiếm mãi |
| Dangling Pointer | Trỏ đến vùng nhớ đã bị thu hồi |
| new/delete | C++ 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ớ.