Giao diện
🌍 Scope & Lifetime — Phạm vi và Vòng đời
Scope quyết định ở đâu có thể truy cập biến. Lifetime quyết định khi nào biến tồn tại. Hiểu hai khái niệm này tránh được nhiều bugs tinh vi.
Scope vs Lifetime
| Concept | Câu hỏi | Ví dụ |
|---|---|---|
| Scope | Biến visible ở đâu? | Block {}, function, file, global |
| Lifetime | Biến tồn tại bao lâu? | Automatic, static, dynamic |
1. Local Variables (Block Scope)
cpp
#include <iostream>
int main() {
// x visible từ đây
int x = 10;
if (true) {
// y chỉ visible trong block này
int y = 20;
std::cout << "x = " << x << std::endl; // OK
std::cout << "y = " << y << std::endl; // OK
}
// y không còn nữa (destroyed)
std::cout << "x = " << x << std::endl; // OK
// std::cout << "y = " << y << std::endl; // ❌ Error: y không tồn tại
return 0;
}Lifetime của Local Variables
cpp
#include <iostream>
void demonstrate() {
int local = 42; // Created khi function được gọi
std::cout << "local = " << local << std::endl;
} // local bị DESTROYED ở đây (stack unwinding)
int main() {
demonstrate(); // local được tạo
demonstrate(); // local mới được tạo (không liên quan cái cũ)
return 0;
}Stack Frame khi gọi demonstrate():
┌─────────────────────┐
│ local = 42 │ ← Tạo khi function start
├─────────────────────┤
│ return address │
└─────────────────────┘
↓
Khi function return, frame bị pop → local destroyed2. Global Variables (File/Namespace Scope)
cpp
#include <iostream>
// Global variable — visible toàn bộ file
int globalCounter = 0;
void increment() {
++globalCounter; // Access global
}
void decrement() {
--globalCounter; // Access same global
}
int main() {
increment();
increment();
increment();
decrement();
std::cout << "globalCounter = " << globalCounter << std::endl; // 2
return 0;
}Lifetime của Global Variables
cpp
// globals.cpp
#include <iostream>
class Logger {
public:
Logger() {
std::cout << "Logger constructed" << std::endl;
}
~Logger() {
std::cout << "Logger destroyed" << std::endl;
}
};
// Global — Constructed BEFORE main(), destroyed AFTER main()
Logger globalLogger;
int main() {
std::cout << "main() starts" << std::endl;
std::cout << "main() ends" << std::endl;
return 0;
}Output:
Logger constructed ← Before main()
main() starts
main() ends
Logger destroyed ← After main()⚠️ Global Variables: Use with Caution!
| Problem | Explanation |
|---|---|
| Hidden dependencies | Functions secretly depend on global state |
| Thread-unsafe | Multiple threads access → race conditions |
| Testing difficulty | Hard to mock/reset between tests |
| Initialization order | Order of construction across files is undefined! |
Best Practice: Minimize globals. Prefer dependency injection.
3. Static Variables
Static Local Variables — "Nhớ" giá trị giữa các lần gọi
cpp
#include <iostream>
void counter() {
static int count = 0; // Initialized ONCE, tồn tại mãi
++count;
std::cout << "Called " << count << " times" << std::endl;
}
int main() {
counter(); // Called 1 times
counter(); // Called 2 times
counter(); // Called 3 times
return 0;
}Visualization:
Lần gọi 1:
┌─────────────────────┐
│ static count = 0 │ → Initialized
│ count = 1 │ → Incremented
└─────────────────────┘
Lần gọi 2:
┌─────────────────────┐
│ static count = 1 │ → Giữ từ lần trước!
│ count = 2 │ → Incremented
└─────────────────────┘
Lần gọi 3:
┌─────────────────────┐
│ static count = 2 │ → Giữ từ lần trước!
│ count = 3 │ → Incremented
└─────────────────────┘Static trong Class (Shared giữa tất cả objects)
cpp
#include <iostream>
class Counter {
public:
static int totalInstances; // Declaration
Counter() {
++totalInstances;
std::cout << "Counter created. Total: " << totalInstances << std::endl;
}
~Counter() {
--totalInstances;
}
};
// Definition của static member (BẮT BUỘC ở file scope)
int Counter::totalInstances = 0;
int main() {
Counter a; // Total: 1
Counter b; // Total: 2
{
Counter c; // Total: 3
} // c destroyed, Total: 2
std::cout << "Final: " << Counter::totalInstances << std::endl; // 2
return 0;
}static ở File Scope — Internal Linkage
cpp
// file1.cpp
static int hiddenVar = 42; // Chỉ visible trong file1.cpp
// file2.cpp
// extern int hiddenVar; // ❌ Linker error: không tìm thấy!4. extern — External Linkage
cpp
// config.h
#ifndef CONFIG_H
#define CONFIG_H
extern int maxConnections; // Declaration (không allocate memory)
extern const char* appName;
#endif
// config.cpp
#include "config.h"
int maxConnections = 100; // Definition (allocate memory)
const char* appName = "PENALGO";
// main.cpp
#include "config.h"
#include <iostream>
int main() {
std::cout << "App: " << appName << std::endl;
std::cout << "Max: " << maxConnections << std::endl;
return 0;
}5. Shadowing — Ẩn biến outer scope
cpp
#include <iostream>
int x = 100; // Global
int main() {
int x = 50; // Local — SHADOWS global x
std::cout << "Local x: " << x << std::endl; // 50
std::cout << "Global x: " << ::x << std::endl; // 100 (scope resolution)
if (true) {
int x = 10; // Shadows local x của main
std::cout << "Inner x: " << x << std::endl; // 10
}
std::cout << "Local x (unchanged): " << x << std::endl; // 50
return 0;
}⚠️ Tránh Shadowing!
Shadowing gây confusion. Các compilers hiện đại có warning -Wshadow:
bash
g++ -Wshadow main.cpp
# warning: declaration of 'x' shadows a global declarationSummary: Storage Duration Table
| Keyword | Scope | Lifetime | Memory Location |
|---|---|---|---|
| (none) — local | Block | Block | Stack |
static (in function) | Block | Program | Data segment |
static (at file scope) | File | Program | Data segment |
extern | Global | Program | Data segment |
new/delete | N/A | Manual | Heap |
🐛 Bug Hunt Challenge
🐛 Bug Hunt
Đoạn code sau có bug gì?
cpp
#include <iostream>
int* createArray() {
int arr[5] = {1, 2, 3, 4, 5};
return arr;
}
int main() {
int* ptr = createArray();
for (int i = 0; i < 5; ++i) {
std::cout << ptr[i] << " ";
}
std::cout << std::endl;
return 0;
}💡 Gợi ý
arr là local variable với lifetime nào?
🔍 Giải thích & Fix
Bug: arr là local array trên stack. Khi createArray() return, stack frame bị pop → memory của arr không còn hợp lệ!
ptr trở thành dangling pointer → Undefined Behavior.
Fix 1 — Static array:
cpp
int* createArray() {
static int arr[5] = {1, 2, 3, 4, 5};
return arr; // static tồn tại cả chương trình
}Fix 2 — Heap allocation:
cpp
int* createArray() {
int* arr = new int[5]{1, 2, 3, 4, 5};
return arr; // Caller phải delete[]!
}Fix 3 — Modern C++ (std::vector):
cpp
#include <vector>
std::vector<int> createArray() {
return {1, 2, 3, 4, 5}; // Return by value, move semantics
}📚 Tổng kết
| Concept | Key Point |
|---|---|
| Local variables | Scope = block, Lifetime = automatic |
| Global variables | Visible everywhere, use sparingly |
| Static local | Remembers value between calls |
| Static file scope | Internal linkage (file-private) |
| extern | Share variables across files |
| Shadowing | Avoid! Use -Wshadow |
➡️ Tiếp theo
Tiếp theo: const Best Practices — Sử dụng const hiệu quả trong function arguments.
🧠 Quiz
Câu 1: Đoạn code sau in ra kết quả gì?
cpp
void counter() {
static int count = 0;
count++;
std::cout << count << " ";
}
int main() {
counter(); counter(); counter();
}- [ ] A) 1 1 1
- [x] B) 1 2 3
- [ ] C) 0 1 2
- [ ] D) 3 3 3
💡 Giải thích: Biến
staticlocal chỉ được khởi tạo một lần duy nhất (lần gọi đầu tiên) và giữ giá trị giữa các lần gọi hàm. Sau mỗi lần gọicounter(),counttăng lên và giữ nguyên giá trị cho lần gọi tiếp theo.
Câu 2: Điều gì xảy ra với biến local sau khi ra khỏi block {}?
- [x] A) Bị hủy (destroyed) tự động
- [ ] B) Vẫn tồn tại nhưng không truy cập được
- [ ] C) Được chuyển thành global variable
- [ ] D) Giá trị bị đặt về 0
💡 Giải thích: Biến local có automatic storage duration — được tạo khi vào block và bị hủy (destructor được gọi) khi ra khỏi block. Bộ nhớ được giải phóng hoàn toàn. Đây là cơ chế RAII quan trọng trong C++.
Câu 3: Variable shadowing là gì và tại sao nên tránh?
- [ ] A) Khai báo biến global cùng tên với function
- [x] B) Khai báo biến trong inner scope cùng tên với outer scope, che khuất biến bên ngoài
- [ ] C) Sử dụng biến chưa được khởi tạo
- [ ] D) Khai báo hai biến cùng tên trong cùng scope
💡 Giải thích: Shadowing xảy ra khi biến trong inner scope cùng tên với outer scope, khiến biến bên ngoài bị "che khuất". Điều này gây nhầm lẫn và bugs tinh vi. Dùng compiler flag
-Wshadowđể phát hiện và tránh shadowing.