Skip to content

🌍 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

ConceptCâu hỏiVí dụ
ScopeBiến visible ở đâu?Block {}, function, file, global
LifetimeBiế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 destroyed

2. 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!

ProblemExplanation
Hidden dependenciesFunctions secretly depend on global state
Thread-unsafeMultiple threads access → race conditions
Testing difficultyHard to mock/reset between tests
Initialization orderOrder 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 declaration

Summary: Storage Duration Table

KeywordScopeLifetimeMemory Location
(none) — localBlockBlockStack
static (in function)BlockProgramData segment
static (at file scope)FileProgramData segment
externGlobalProgramData segment
new/deleteN/AManualHeap

🐛 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

ConceptKey Point
Local variablesScope = block, Lifetime = automatic
Global variablesVisible everywhere, use sparingly
Static localRemembers value between calls
Static file scopeInternal linkage (file-private)
externShare variables across files
ShadowingAvoid! Use -Wshadow

➡️ Tiếp theo

Tiếp theo: const Best Practices — Sử dụng const hiệu quả trong function arguments.