Con trỏ trong C (pointer): giải thích dễ hiểu qua ví dụ

  • author-image

    Nguyễn Đình Cường

  • blog-tag
  • blog-comment 0 comment
  • created-date 09 Nov, 2026
blog-thumbnail

Con trỏ là chủ đề khiến nhiều người mới học C phát hoảng, nhưng khái niệm cốt lõi rất đơn giản: con trỏ là một biến lưu địa chỉ của biến khác. Hiểu nó giúp bạn nắm được bộ nhớ hoạt động ra sao.

Bộ nhớ và địa chỉ

Hãy hình dung bộ nhớ như một dãy dài các ô, mỗi ô có số thứ tự gọi là địa chỉ. Khi bạn khai báo int x = 5;, máy cấp một vài ô để chứa giá trị 5 và biến x là tên gắn với vùng đó. Có hai thứ liên quan đến x: giá trị (5) và địa chỉ (ô nằm ở đâu).

Hai toán tử then chốt

  • &x: lấy địa chỉ của biến x.
  • *p: truy cập giá trị tại địa chỉ mà con trỏ p đang giữ (gọi là giải tham chiếu).
#include <stdio.h>

int main(void) {
    int x = 5;
    int *p = &x;     // p lưu địa chỉ của x
    printf("x = %d\n", x);
    printf("*p = %d\n", *p);   // 5
    *p = 10;                   // sửa x thông qua p
    printf("x sau khi sua = %d\n", x);   // 10
    return 0;
}

Dấu * xuất hiện ở hai ngữ cảnh: khi khai báo (int *p) nghĩa là "p là con trỏ tới int", còn khi dùng (*p) nghĩa là "giá trị tại địa chỉ p trỏ tới".

Vì sao cần con trỏ?

Cho phép hàm sửa biến bên ngoài

C truyền tham số theo giá trị, tức hàm nhận một bản sao. Muốn hàm thay đổi biến gốc, ta truyền địa chỉ của nó:

#include <stdio.h>

void hoan_doi(int *a, int *b) {
    int tam = *a;
    *a = *b;
    *b = tam;
}

int main(void) {
    int x = 1, y = 2;
    hoan_doi(&x, &y);
    printf("%d %d\n", x, y);   // 2 1
    return 0;
}

Làm việc với mảng

Tên mảng gần như là con trỏ đến phần tử đầu tiên. Do đó a[i] tương đương *(a + i):

int a[3] = {10, 20, 30};
int *p = a;
printf("%d\n", *(p + 1));   // 20
printf("%d\n", p[2]);       // 30

Cộng một số vào con trỏ nghĩa là nhảy đúng bấy nhiêu phần tử của kiểu đó, chứ không phải bấy nhiêu byte.

Cấp phát bộ nhớ động

Khi chưa biết trước cần bao nhiêu phần tử, ta xin bộ nhớ lúc chương trình chạy bằng malloc và phải trả lại bằng free:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int n = 3;
    int *ds = malloc(n * sizeof(int));
    if (ds == NULL) {
        return 1;           // xin bộ nhớ thất bại
    }
    for (int i = 0; i < n; i++) {
        ds[i] = (i + 1) * 10;
    }
    printf("%d\n", ds[2]);  // 30
    free(ds);
    return 0;
}

Các lỗi nguy hiểm với con trỏ

  1. Con trỏ chưa khởi tạo: trỏ đến chỗ ngẫu nhiên, ghi vào đó gây lỗi khó lường. Hãy gán NULL nếu chưa có địa chỉ.
  2. Giải tham chiếu NULL: truy cập *p khi p là NULL làm chương trình sập.
  3. Rò rỉ bộ nhớ: dùng malloc mà quên free.
  4. Dùng sau khi giải phóng: truy cập vùng nhớ đã free.
  5. Vượt biên mảng: ghi ngoài vùng được cấp phát.

Các công cụ như trình gỡ lỗi và các bộ kiểm tra bộ nhớ giúp phát hiện những lỗi này khi bạn thực hành.

Mẹo học con trỏ

  • Vẽ bộ nhớ ra giấy: ô nào chứa gì, mũi tên từ con trỏ đến đích.
  • In địa chỉ bằng printf("%p", (void*)&x) để quan sát.
  • Bắt đầu từ các ví dụ nhỏ như hoán đổi, đảo mảng, rồi mới tới danh sách liên kết.

Bài tập luyện con trỏ

Hãy làm theo thứ tự tăng dần độ khó, vừa làm vừa vẽ sơ đồ bộ nhớ:

  1. Viết hàm void tang_doi(int *x) nhân đôi giá trị của biến được truyền vào.
  2. Viết hàm tìm giá trị lớn nhất trong mảng nhận vào con trỏ và độ dài.
  3. Viết hàm đảo ngược mảng tại chỗ bằng hai con trỏ chạy từ hai đầu vào giữa.
  4. Tạo mảng động bằng malloc, nhập n số, tính tổng, rồi giải phóng.
#include <stdio.h>

void dao_mang(int *dau, int n) {
    int *cuoi = dau + n - 1;
    while (dau < cuoi) {
        int tam = *dau;
        *dau = *cuoi;
        *cuoi = tam;
        dau++;
        cuoi--;
    }
}

int main(void) {
    int a[5] = {1, 2, 3, 4, 5};
    dao_mang(a, 5);
    for (int i = 0; i < 5; i++) printf("%d ", a[i]);   // 5 4 3 2 1
    printf("
");
    return 0;
}

Bài đảo mảng cho thấy hai con trỏ cùng thuộc một mảng có thể so sánh với nhau và di chuyển bằng ++, --. Khi đã thạo, bạn chuyển sang danh sách liên kết, nơi con trỏ là trung tâm của mọi thứ.

Kết luận

Con trỏ là biến chứa địa chỉ, & lấy địa chỉ, * lấy giá trị tại địa chỉ. Nắm vững ba ý này cùng nguyên tắc "xin bộ nhớ thì phải trả", bạn đã vượt qua phần khó nhất của C và sẵn sàng viết các cấu trúc dữ liệu như danh sách liên kết hay cây.

Nếu cần một lộ trình rõ ràng thay vì tự mò mẫm, codebem.com có nhiều khóa học phù hợp cho người mới.

author_photo
Nguyễn Đình Cường

0 comment