Skip to content

🎯 Pointers & References — The Beast

Đây là phần QUAN TRỌNG NHẤT của C++. Nếu bạn hiểu Pointers và References, bạn hiểu C++. Nếu không, bạn sẽ mãi đau khổ với bugs khó hiểu.

Khái niệm cơ bản: Địa chỉ bộ nhớ

Mỗi biến trong bộ nhớ có một địa chỉ — vị trí cụ thể trong RAM:

cpp
#include <iostream>

int main() {
    int x = 42;
    
    std::cout << "Giá trị của x: " << x << std::endl;
    std::cout << "Địa chỉ của x: " << &x << std::endl;
    
    return 0;
}

Output:

Giá trị của x: 42
Địa chỉ của x: 0x7ffd5c3b3a4c

Visualization:

                    RAM
        ┌─────────────────────┐
0x...3a48│         ...         │
        ├─────────────────────┤
0x...3a4c│         42          │ ← x lives here (4 bytes for int)
        ├─────────────────────┤
0x...3a50│         ...         │
        └─────────────────────┘

Pointer: Biến chứa địa chỉ

Pointer là biến đặc biệt — giá trị của nó là địa chỉ của biến khác:

cpp
#include <iostream>

int main() {
    int x = 42;
    
    // Khai báo pointer: type* name
    int* ptr = &x;  // ptr chứa địa chỉ của x
    
    std::cout << "x = " << x << std::endl;           // 42
    std::cout << "&x = " << &x << std::endl;          // 0x7ffd...
    std::cout << "ptr = " << ptr << std::endl;        // 0x7ffd... (same as &x)
    std::cout << "*ptr = " << *ptr << std::endl;      // 42 (dereference)
    
    return 0;
}

Visualization:

┌──────────────────────────────────────────────────────────────┐
│                          STACK                                │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│   x        ┌────────┐                                        │
│   (int)    │   42   │  ← Địa chỉ: 0x7ffd3a4c                │
│            └────────┘                                        │
│                 ▲                                            │
│                 │ ptr trỏ đến x                              │
│                 │                                            │
│   ptr      ┌────────────────┐                                │
│   (int*)   │  0x7ffd3a4c    │  ← ptr chứa địa chỉ của x     │
│            └────────────────┘                                │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Hai toán tử quan trọng: &*

Toán tửTênÝ nghĩaVí dụ
&Address-ofLấy địa chỉ của biến&x → địa chỉ của x
*DereferenceLấy giá trị tại địa chỉ*ptr → giá trị mà ptr trỏ đến
cpp
#include <iostream>

int main() {
    int x = 100;
    int* ptr = &x;
    
    // Thay đổi giá trị thông qua pointer
    *ptr = 200;
    
    std::cout << "x sau khi *ptr = 200: " << x << std::endl;  // 200!
    
    return 0;
}

🔑 Nguyên tắc vàng

  • &variable → "Cho tôi địa chỉ của variable"
  • *pointer → "Cho tôi giá trị tại địa chỉ mà pointer chứa"

Reference: Bí danh cho biến

Referencealias — tên khác cho cùng một biến:

cpp
#include <iostream>

int main() {
    int x = 42;
    int& ref = x;  // ref là alias của x
    
    std::cout << "x = " << x << std::endl;      // 42
    std::cout << "ref = " << ref << std::endl;  // 42
    std::cout << "&x = " << &x << std::endl;    // 0x...
    std::cout << "&ref = " << &ref << std::endl; // 0x... (SAME!)
    
    // Thay đổi ref = thay đổi x
    ref = 100;
    std::cout << "x sau ref = 100: " << x << std::endl;  // 100
    
    return 0;
}

So sánh với Pointer:

Đặc điểmPointer (int*)Reference (int&)
Có thể null?✅ (nullptr)❌ Phải bind khi khai báo
Có thể rebind?ptr = &y;❌ Một lần là mãi mãi
Syntax để truy cập*ptrTrực tiếp ref
Có thể là member?⚠️ Phải khởi tạo trong constructor
cpp
int x = 10, y = 20;

// Pointer có thể rebind
int* ptr = &x;
ptr = &y;  // ✅ OK, ptr giờ trỏ đến y

// Reference KHÔNG thể rebind
int& ref = x;
ref = y;   // ⚠️ Đây là gán giá trị! x = 20, ref vẫn alias của x

Pass by Value vs Pointer vs Reference

cpp
#include <iostream>

// Pass by Value — nhận BẢN SAO
void byValue(int n) {
    n = 999;  // Chỉ thay đổi bản sao local
}

// Pass by Pointer — nhận ĐỊA CHỈ
void byPointer(int* ptr) {
    *ptr = 777;  // Thay đổi gốc thông qua dereference
}

// Pass by Reference — nhận ALIAS
void byReference(int& ref) {
    ref = 555;  // Thay đổi gốc trực tiếp
}

int main() {
    int a = 100, b = 100, c = 100;
    
    byValue(a);
    std::cout << "After byValue: a = " << a << std::endl;     // 100 (unchanged)
    
    byPointer(&b);
    std::cout << "After byPointer: b = " << b << std::endl;   // 777
    
    byReference(c);
    std::cout << "After byReference: c = " << c << std::endl; // 555
    
    return 0;
}

📌 HPN Best Practice: Khi nào dùng gì?

SituationRecommendation
Không cần modify, primitive nhỏPass by value
Không cần modify, object lớnconst T& (const reference)
Cần modifyT& (reference)
Có thể null / optionalT* (pointer) + nullptr check
ArraysPointer (decay to pointer)

nullptr vs NULL (Modern C++)

cpp
#include <iostream>

void process(int n) {
    std::cout << "int version: " << n << std::endl;
}

void process(int* ptr) {
    std::cout << "pointer version" << std::endl;
}

int main() {
    // ❌ NULL có thể gây ambiguity
    // process(NULL);  // Có thể gọi int version! (NULL = 0)
    
    // ✅ nullptr luôn là pointer type
    process(nullptr);  // Luôn gọi pointer version
    
    int* ptr = nullptr;  // Modern way to init null pointer
    
    if (ptr == nullptr) {
        std::cout << "ptr is null" << std::endl;
    }
    
    // Also works with if(!ptr)
    if (!ptr) {
        std::cout << "ptr is null (boolean check)" << std::endl;
    }
    
    return 0;
}

⚠️ Luôn dùng nullptr thay vì NULL

NULL được định nghĩa là 0 (số nguyên) trong nhiều compilers, gây ambiguity với overloaded functions.


Pointer Arithmetic (cho Arrays)

cpp
#include <iostream>

int main() {
    int arr[] = {10, 20, 30, 40, 50};
    int* ptr = arr;  // ptr trỏ đến arr[0]
    
    std::cout << "=== Pointer Arithmetic ===" << std::endl;
    
    for (int i = 0; i < 5; ++i) {
        // Hai cách truy cập tương đương:
        std::cout << "arr[" << i << "] = " << arr[i];
        std::cout << " | *(ptr+" << i << ") = " << *(ptr + i) << std::endl;
    }
    
    // ptr++ di chuyển sizeof(int) = 4 bytes
    ptr++;  // ptr giờ trỏ đến arr[1]
    std::cout << "\nAfter ptr++: *ptr = " << *ptr << std::endl;  // 20
    
    return 0;
}

Visualization:

       arr:  ┌────┬────┬────┬────┬────┐
             │ 10 │ 20 │ 30 │ 40 │ 50 │
             └────┴────┴────┴────┴────┘
Address:    0x100 0x104 0x108 0x10c 0x110

             ptr (ban đầu)
              
After ptr++:
             ┌────┬────┬────┬────┬────┐
             │ 10 │ 20 │ 30 │ 40 │ 50 │
             └────┴────┴────┴────┴────┘

                  ptr (sau ptr++)

Dangling Pointer & Memory Safety

cpp
#include <iostream>

// ❌ NGUY HIỂM: Trả về pointer đến local variable!
int* dangerous() {
    int local = 42;
    return &local;  // local bị destroy khi function ends!
}

// ✅ AN TOÀN: Cấp phát trên heap
int* safe() {
    int* heapVar = new int(42);
    return heapVar;  // Caller phải delete!
}

int main() {
    // ❌ Dangling pointer
    int* bad = dangerous();
    // *bad là undefined behavior — có thể crash, có thể ra số rác
    
    // ✅ Heap allocation
    int* good = safe();
    std::cout << "*good = " << *good << std::endl;  // 42
    delete good;  // Don't forget!
    good = nullptr;  // Good practice
    
    return 0;
}

☠️ Dangling Pointer

Pointer trỏ đến vùng nhớ đã bị giải phóng. Truy cập sẽ gây Undefined Behavior — chương trình có thể crash, hoặc tệ hơn, chạy sai âm thầm!


🐛 Spot the Bug: Pointer Edition

🐛 Bug Hunt Challenge #1

Chương trình sau sẽ in ra gì?

cpp
#include <iostream>

void swap(int* a, int* b) {
    int* temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 10, y = 20;
    swap(&x, &y);
    std::cout << "x = " << x << ", y = " << y << std::endl;
    return 0;
}
💡 Gợi ý

Chúng ta đang swap pointer values hay swap giá trị mà pointer trỏ đến?

🔍 Giải thích & Fix

Output: x = 10, y = 20 — Không swap được!

Bug: Hàm swap đang swap bản sao của pointers (parameters), không phải giá trị.

Fix:

cpp
void swap(int* a, int* b) {
    int temp = *a;  // Dereference!
    *a = *b;
    *b = temp;
}

Hoặc dùng references (cleaner):

cpp
void swap(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

// Gọi: swap(x, y); — không cần &

🐛 Bug Hunt Challenge #2

Chương trình sau có bug gì?

cpp
#include <iostream>

int main() {
    int* ptr;  // Uninitialized!
    *ptr = 42;
    std::cout << *ptr << std::endl;
    return 0;
}
🔍 Giải thích

Bug: ptr chưa được khởi tạo — nó chứa garbage value (địa chỉ ngẫu nhiên).

*ptr = 42 ghi vào vùng nhớ random → Segmentation Fault hoặc corrupt memory!

Fix:

cpp
int x;
int* ptr = &x;  // Trỏ đến biến hợp lệ
*ptr = 42;

// Hoặc:
int* ptr = new int(42);  // Cấp phát heap
// ... use ptr
delete ptr;

📚 Tổng kết

ConceptKey Takeaway
Pointer (int*)Biến chứa địa chỉ, dùng * để dereference
Reference (int&)Alias, phải bind khi khai báo, không rebind
&variableAddress-of operator — lấy địa chỉ
*pointerDereference — lấy giá trị tại địa chỉ
nullptrModern null pointer (thay NULL)
Pass by pointer/refCho phép modify biến gốc
Dangling pointer☠️ Trỏ đến memory đã bị free — UB!

➡️ Tiếp theo

Bạn đã hiểu Pointers & References! Tiếp theo: Input/Output — Giao tiếp với người dùng qua std::cin/cout.