Bài 4 · Nâng cao · 24 phút· Cập nhật 11/06/2026
Con trỏ & mảng
Biên soạn bởi Nguyễn Anh Tuấn
Con trỏ và mảng trong C (pointer & array): tên mảng là địa chỉ phần tử đầu, số học con trỏ, a[i] = *(a+i) và bẫy mảng suy biến (decay) khi truyền vào hàm.
Điểm mấu chốt: tên mảng là địa chỉ của phần tử đầu tiên. Viết a tương đương &a[0]. Vì thế gán một mảng cho con trỏ không cần dấu &:
ten-mang.c
#include <stdio.h>
int main() {
int a[4] = {10, 20, 30, 40};
int *p = a; // a la dia chi a[0]; khong can &
printf("%d\n", *p); // 10 - phan tu dau
printf("%d\n", a == &a[0]); // 1 - dung, bang nhau
return 0;
} Kết quả khi chạy
10 1
- ▸a ≡ &a[0] - tên mảng là địa chỉ phần tử đầu.
- ▸int *p = a; hợp lệ và KHÔNG cần & (mảng đã là địa chỉ).
- ▸Với biến thường vẫn phải &x; chỉ mảng mới đặc biệt thế này.
Phép lập chỉ số a[i] chỉ là đường tắt cho số học con trỏ: *(a + i). Kéo k để di chuyển con trỏ p = a + k dọc mảng và xem ba cách viết luôn cho cùng một ô:
Tăng k lên 1, địa chỉ nhảy 4 byte (= sizeof int) chứ không phải 1 - con trỏ “biết” kích thước phần tử. Đó là lý do a[i] và *(a + i) luôn cho cùng một ô.
index-vs-ptr.c
int a[4] = {10, 20, 30, 40};
printf("%d\n", a[2]); // 30
printf("%d\n", *(a + 2)); // 30 - giong het a[2] Kết quả khi chạy
30 30
Điểm tinh tế: p + 1 không cộng 1 vào địa chỉ - nó nhảy sizeof(phần tử) byte. Con trỏ biết kiểu nó trỏ tới, nên luôn rơi đúng phần tử kế tiếp:
ptr-math.c
int a[4] = {10, 20, 30, 40};
int *p = a; // p tro a[0] (vd 0x100)
printf("%d\n", *p); // 10
p = p + 1; // nhay 4 byte (sizeof int) -> a[1] (0x104)
printf("%d\n", *p); // 20 Kết quả khi chạy
10 20
- ▸p + i → địa chỉ + i × sizeof(*p) byte (KHÔNG phải + i byte).
- ▸int* nhảy 4, double* nhảy 8, char* nhảy 1 mỗi bước.
- ▸Nhờ vậy a + i luôn trỏ đúng a[i] dù phần tử to nhỏ thế nào.
Hai cách duyệt mảng tương đương: bằng chỉ số a[i] hoặc bằng con trỏ chạy p++ từ a tới a + n. Đây cũng đúng cách duyệt một chuỗi bằng con trỏ char:
duyet-con-tro.c
#include <stdio.h>
int main() {
int a[4] = {10, 20, 30, 40};
for (int *p = a; p < a + 4; p++) { // p chay tu a[0] toi a[3]
printf("%d ", *p);
}
printf("\n");
return 0;
} Kết quả khi chạy
10 20 30 40
Mảng và con trỏ rất giống nhưng không đồng nhất. Khác biệt lộ ra ở sizeof:
sizeof.c
int a[4];
int *p = a;
printf("%zu\n", sizeof(a)); // 16 - ca mang (4 * 4 byte)
printf("%zu\n", sizeof(p)); // 8 - chi 1 con tro Kết quả khi chạy
16 8
Mảng “suy biến” khi truyền vào hàm
Câu hỏi thường gặp
Vì bản thân tên mảng a ĐÃ là một địa chỉ - địa chỉ của phần tử đầu a[0]. Viết a tương đương &a[0]. Với một biến thường thì cần &x, nhưng mảng thì không.
Có. Trình biên dịch DỊCH a[i] thành *(a + i). Vì phép cộng giao hoán nên *(a + i) = *(i + a), tức i[a] cũng hợp lệ (đừng viết thế, nhưng nó cho thấy a[i] thuần tuý là số học con trỏ).
Nhảy sizeof(*p) byte - kích thước MỘT phần tử. int* nhảy 4, double* nhảy 8, char* nhảy 1. Con trỏ “biết” kiểu nó trỏ tới, nên a + i luôn rơi đúng vào phần tử thứ i dù mỗi phần tử to nhỏ khác nhau.
Giá trị (con số địa chỉ) giống nhau, nhưng KIỂU khác: a là int * (trỏ tới một int), còn &a là int (*)[4] (trỏ tới CẢ mảng). Hệ quả: a + 1 nhảy 4 byte (một int), nhưng &a + 1 nhảy 16 byte (cả mảng). Chi tiết nhỏ, nhưng giải thích nhiều lỗi khó hiểu.
a là một MẢNG thật - trình biên dịch biết nó gồm 4 int nên sizeof(a) = 16. p chỉ là một con trỏ, sizeof(p) = 8 (kích thước một địa chỉ trên 64-bit). Đừng dùng sizeof để đếm phần tử qua một con trỏ!
Mảng “suy biến” (decay) thành con trỏ tới phần tử đầu. void f(int arr[]) thực chất là void f(int *arr). Bên trong hàm, sizeof(arr) = 8 (con trỏ), KHÔNG phải kích thước mảng → bạn phải truyền thêm số phần tử n.
Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.
Trả lời vài câu để chắc rằng em đã nắm bài.
Với int a[4]; biểu thức a[i] tương đương cách viết nào bằng con trỏ?
- 1
Địa chỉ của p
Dùng công cụ ở Bước 2 với
k = 2(mảng bắt đầu ở0x100).p = a + 2có địa chỉ gì?*pbằng bao nhiêu?Hoàn thành khi:
0x108(=0x100+ 2×4) và*p= 30. Cộng 2 nhảy 8 byte, không phải 2. - 2
Viết lại bằng con trỏ
Viết
a[3]dưới dạng dùng dấu*(dereference).Hoàn thành khi:
*(a + 3)- hoàn toàn tương đươnga[3]. - 3
Bước nhảy
p + 1nhảy bao nhiêu byte nếuplàint*? Nếuplàdouble*(double= 8 byte)?Hoàn thành khi:
int*: 4 byte.double*: 8 byte. Luôn bằngsizeofphần tửptrỏ tới. - 4
Duyệt bằng con trỏ
Viết vòng
forduyệtint a[5]bằng CON TRỎ (không dùnga[i]) để in từng phần tử.Hoàn thành khi:
for (int *p = a; p < a + 5; p++) printf("%d ", *p); - 5
sizeof mảng
Cho
int a[10];(int= 4 byte).sizeof(a)bằng bao nhiêu? Còn vớiint *p = a;thìsizeof(p)?Hoàn thành khi:
sizeof(a)= 40 (10×4).sizeof(p)= 8 (một con trỏ). Khác nhau! - 6
Vì sao cần n?
Trong
void in(int arr[], int n), vì sao bắt buộc có tham sốn?Hoàn thành khi: Vì
arrđã suy biến thành con trỏ - hàm không biết mảng dài bao nhiêu. Phải truyềnnđể biết khi nào dừng.