← Lập trình C nâng cao

Bài 1 · Nâng cao · 24 phút· Cập nhật 11/06/2026

Con trỏ & địa chỉ

Biên soạn bởi Nguyễn Anh Tuấn

Con trỏ (pointer) trong C: mọi biến có một địa chỉ trong RAM; & lấy địa chỉ, * (dereference) truy cập giá trị - học với mô phỏng RAM trực quan.

Bộ nhớ (RAM) là một dãy ô, mỗi ô có một địa chỉ (như số nhà). Khi bạn viết int x = 5;, máy dành một ô ở một địa chỉ nào đó và đặt 5 vào. Toán tử & (address-of) cho ta địa chỉ ấy:

  • Mỗi biến có HAI thứ: một giá trị, và một địa chỉ nơi nó được lưu.
  • &x đọc là “địa chỉ của x” - một con số (thường in dạng hex 0x…).
  • Địa chỉ thực đổi mỗi lần chạy; điều quan trọng là KHÁI NIỆM, không phải con số cụ thể.

Trung thực: đây là mô hình dạy học

Mô phỏng dưới đây hiển thị mỗi biến là một hàng tại một địa chỉ (không vẽ từng byte), địa chỉ bắt đầu từ 0x100 cho dễ nhìn. Kích thước là thật: int = 4 byte, con trỏ = 8 byte (hệ 64-bit). Trong máy thật, địa chỉ lớn và ngẫu nhiên hơn - nhưng cách con trỏ hoạt động thì đúng y như vậy.

Chuẩn C của khoá: C17

Như khoá Cơ bản, mọi code trong khoá này viết theo chuẩn C17 và biên dịch với bộ cờ -std=c17 -Wall -Wextra. Chạy lại ví dụ trên máy, mèo con cứ giữ đúng bộ cờ đó.

Con trỏ là biến lưu địa chỉ của biến khác. int *px = &x; tạo con trỏ px và cho nó giữ địa chỉ của x - ta nói “px trỏ tới x”. Bấm Bước tiếp để xem từng câu lệnh tác động lên bộ nhớ; để ý mũi tên px → x:

Bước 1/6
con-tro.c
Địa chỉBiếnGiá trị
0x100 int x 5
int x = 5; - Cấp phát x tại địa chỉ 0x100 (4 byte), gán 5.
  • Khai báo: kiểu *tên; vd int *px; (px trỏ tới một int).
  • px = &x; cho px giữ địa chỉ của x → mũi tên px trỏ sang x.
  • Giá trị của con trỏ chính là một địa chỉ (xem cột Giá trị: 0x100).

Có địa chỉ rồi, dùng *px (dereference) để đi tới ô đó và làm việc với giá trị. Đây mới là chỗ con trỏ phát huy tác dụng: *px = 99; ghi trực tiếp vào x dù ta không hề nhắc tên x:

deref.c

#include <stdio.h>

int main() {
    int x = 5;
    int *px = &x;       // px tro toi x

    printf("%d\n", *px);  // doc qua con tro -> 5
    *px = 99;             // ghi qua con tro -> doi x
    printf("%d\n", x);    // x da thanh 99!
    return 0;
}

Kết quả khi chạy

5
99
  • *px ở VẾ PHẢI: đọc giá trị tại ô px trỏ tới.
  • *px ở VẾ TRÁI: ghi giá trị vào ô px trỏ tới.
  • *(&x) chính là x - & và * là hai phép ngược nhau.

Một con trỏ phải trỏ tới đâu đó hợp lệ trước khi dereference. Hai tình huống nguy hiểm: con trỏ NULL (cố ý “rỗng”) và con trỏ chưa khởi tạo (chứa địa chỉ rác):

null.c

#include <stdio.h>

int main() {
    int *p = NULL;          // ro rang: chua tro vao dau
    if (p == NULL) {
        printf("p chua tro vao dau\n");
    }
    // *p = 5;  <- neu bo dau // se SAP (segfault)

    int *q;                 // con tro HOANG: dia chi rac
    // *q = 7;  <- ghi vao vung nho bat ky -> nguy hiem
    return 0;
}

Kết quả khi chạy

p chua tro vao dau

Quy tắc vàng

Luôn khởi tạo con trỏ: cho nó = &biến hợp lệ, hoặc = NULL rồi kiểm tra if (p != NULL) trước khi dùng *p. Dereference con trỏ NULL hay con trỏ hoang là nguồn của lỗi “segmentation fault” kinh điển.

Người mới học con trỏ hay lẫn lộn ba thứ sau - phân biệt rõ là qua được nửa chặng đường:

  • px ≠ *px: px là ĐỊA CHỈ (số nhà); *px là GIÁ TRỊ trong căn nhà đó.
  • & (lấy địa chỉ) ngược với * (truy cập giá trị) - đừng dùng nhầm.
  • int *px, y; → px là con trỏ, y CHỈ là int (dấu * gắn với px thôi).
  • Gán px = x (thiếu &) là sai kiểu: px cần một địa chỉ, không phải giá trị.

Mẹo đọc khai báo

Đọc int *px ngược lại: “*px là một int” ⇒ px phải là con trỏ tới int. Cách đọc này cứu bạn ở những khai báo phức tạp hơn sau này.

Câu hỏi thường gặp

Là một biến đặc biệt lưu ĐỊA CHỈ của một biến khác, thay vì lưu giá trị trực tiếp. Nói “px trỏ tới x” nghĩa là px chứa địa chỉ ô nhớ của x. Từ địa chỉ đó, ta đọc/ghi được chính x.

& (address-of) lấy ĐỊA CHỈ của một biến: &x. * (dereference) đi tới địa chỉ và truy cập GIÁ TRỊ ở đó: *px. Chúng ngược nhau - *(&x) chính là x. Lưu ý * còn dùng khi KHAI BÁO con trỏ (int *px), nghĩa khác với * khi dùng (dereference).

Gắn với BIẾN. int *px, y; thì px là con trỏ-tới-int, còn y chỉ là int thường. Muốn hai con trỏ phải viết int *a, *b;. Nhiều người viết int* px cho dễ đọc, nhưng cái bẫy ở trên vẫn đúng.

Để (1) hàm sửa được biến của nơi gọi (truyền địa chỉ - bài sau); (2) cấp phát bộ nhớ động (malloc); (3) xây cấu trúc liên kết (danh sách, cây); (4) truyền dữ liệu lớn mà không sao chép. Con trỏ là cánh cửa vào phần bản chất của C.

Bài Con trỏ & hàm →

NULL là giá trị đặc biệt nghĩa là “con trỏ KHÔNG trỏ vào đâu cả”. Dùng nó để khởi tạo con trỏ an toàn và để kiểm tra (if (p != NULL)) trước khi truy cập. Dereference một con trỏ NULL sẽ làm chương trình sập (segfault).

int *p; (chưa gán) chứa một địa chỉ RÁC ngẫu nhiên. *p = 5; sẽ ghi vào một vùng nhớ bất kỳ - có thể phá dữ liệu khác hoặc làm sập chương trình kiểu rất khó lần ra nguyên nhân. Gọi là con trỏ hoang (wild pointer). Luôn khởi tạo con trỏ (= &biến hoặc = NULL).

Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.

Tick những điều em tự tin làm được sau khi học bài này. 0/6

Trả lời vài câu để chắc rằng em đã nắm bài.

Câu 1/3 Điểm: 0

Trong int x = 5; int *px = &x; thì giá trị lưu trong px là gì?

  1. 1

    px có đổi không?

    Dùng mô phỏng ở Bước 2, tới bước “*px = 99;”. Giá trị lưu trong px có thay đổi không? Còn x?

    Hoàn thành khi: px KHÔNG đổi (vẫn giữ địa chỉ 0x100 của x); chỉ ô x đổi thành 99. *px = 99 ghi vào ô px trỏ tới.

  2. 2

    Lấy giá trị qua con trỏ

    Cho int a = 7; int *pa = &a; thì *pa bằng bao nhiêu?

    Hoàn thành khi: 7 - *pa đi tới địa chỉ pa lưu (= &a) và đọc giá trị ở đó, chính là a.

  3. 3

    Sửa qua con trỏ

    Tiếp bài trên, chạy *pa = 20; rồi in a. a bằng bao nhiêu?

    Hoàn thành khi: 20 - ghi qua con trỏ đổi luôn biến gốc a.

  4. 4

    Con trỏ rỗng

    Viết khai báo một con trỏ int chưa trỏ vào đâu một cách an toàn.

    Hoàn thành khi: int *p = NULL; - rồi kiểm tra if (p != NULL) trước khi dùng *p.

  5. 5

    px so với *px

    Cho int *px = &x; với x = 42. printf("%p") cho px in ra gì, còn printf("%d", *px) in ra gì (về ý nghĩa)?

    Hoàn thành khi: px in ra một ĐỊA CHỈ (vd 0x...); *px in ra GIÁ TRỊ tại địa chỉ đó = 42.

  6. 6

    Vì sao con trỏ 8 byte?

    Con trỏ int chiếm 8 byte (trên 64-bit) dù int chỉ 4 byte. Vì sao?

    Hoàn thành khi: Con trỏ lưu một ĐỊA CHỈ bộ nhớ (rộng 8 byte trên hệ 64-bit), không phụ thuộc kích thước biến nó trỏ tới. Trên các nền tảng thông dụng, mọi con trỏ dữ liệu đều cùng kích thước.