Bài 2 · Nâng cao · 22 phút· Cập nhật 11/06/2026
Con trỏ & hàm
Biên soạn bởi Nguyễn Anh Tuấn
Con trỏ và hàm trong C: truyền địa chỉ (pass by pointer) để hàm sửa được biến gốc; viết hàm hoanVi (swap), out-parameter và bẫy dangling pointer.
Ở khoá cơ bản ta thấy: hàm nhận bản sao của đối số, nên sửa tham số bên trong không đổi biến gốc. Thử viết hoanVi kiểu “ngây thơ” - nó thất bại:
swap-sai.c
#include <stdio.h>
void hoanViSai(int x, int y) { // nhan BAN SAO
int t = x; x = y; y = t; // chi doi ban sao
}
int main() {
int a = 3, b = 7;
hoanViSai(a, b);
printf("a=%d b=%d\n", a, b); // van 3 va 7!
return 0;
} Kết quả khi chạy
a=3 b=7
Vấn đề
Giải pháp: truyền địa chỉ của a và b (&a, &b) vào các tham số con trỏ. Bên trong hàm, *pa chính là a - ghi vào *pa là ghi thẳng vào a. Bấm Bước tiếp để xem swap diễn ra trong bộ nhớ:
swap-dung.c
#include <stdio.h>
void hoanVi(int *pa, int *pb) { // nhan DIA CHI
int t = *pa; // giu gia tri cu cua a
*pa = *pb; // a <- b
*pb = t; // b <- a (cu)
}
int main() {
int a = 3, b = 7;
hoanVi(&a, &b); // truyen dia chi cua a, b
printf("a=%d b=%d\n", a, b); // 7 va 3 - da hoan vi!
return 0;
} Kết quả khi chạy
a=7 b=3
- ▸Tham số con trỏ: void hoanVi(int *pa, int *pb).
- ▸Gọi với địa chỉ: hoanVi(&a, &b) - KHÔNG phải hoanVi(a, b).
- ▸Trong hàm, *pa là biến gốc a; ghi *pa = … đổi luôn a ở main.
Còn nhớ scanf("%d", &x) ở khoá cơ bản? Dấu & chính là “truyền địa chỉ để hàm ghi vào biến”. scanf cần biết x ở đâu để đặt giá trị đọc được vào đó:
scanf-la-con-tro.c
int tuoi;
scanf("%d", &tuoi); // & : dua DIA CHI de scanf ghi ket qua vao tuoi
printf("%d", tuoi); // khong & : chi can GIA TRI de in - ▸scanf GHI vào biến ⇒ cần địa chỉ (&tuoi).
- ▸printf chỉ ĐỌC biến ⇒ truyền giá trị là đủ (không &).
- ▸Quên & trong scanf là lỗi kinh điển - scanf ghi vào địa chỉ rác.
return chỉ trả một giá trị. Muốn một hàm trả nhiều kết quả, truyền thêm các con trỏ làm “tham số ra” (out-parameter) để hàm ghi kết quả vào:
chia-lay-du.c
#include <stdio.h>
void chia(int tu, int mau, int *thuong, int *du) {
*thuong = tu / mau; // ghi thuong ra ngoai
*du = tu % mau; // ghi du ra ngoai
}
int main() {
int q, r;
chia(17, 5, &q, &r);
printf("%d du %d\n", q, r); // 3 du 2
return 0;
} Kết quả khi chạy
3 du 2
Lỗi nguy hiểm nhất khi trả con trỏ từ hàm: trỏ tới biến cục bộ đã chết.
dangling.c
int* taoSai() {
int n = 5;
return &n; // SAI: n bien mat khi ham ket thuc
} // -> con tro "treo" (dangling), tro toi vung nho da thu hoi
// Trinh bien dich CANH BAO: "address of stack memory returned" Ghi nhớ
Câu hỏi thường gặp
Vì hàm nhận một BẢN SAO của đối số (Bài Hàm ở khoá cơ bản). Sửa bản sao không đụng bản gốc. Muốn sửa bản gốc, hàm phải biết nó NẰM Ở ĐÂU - tức là nhận địa chỉ của nó (con trỏ).
Bài Hàm - khoá C cơ bản →Bạn truyền &a (địa chỉ của a) vào một tham số con trỏ int *pa. Bên trong hàm, *pa chính là a - ghi vào *pa là ghi thẳng vào a ở nơi gọi. Hàm không cần biết tên a, chỉ cần địa chỉ.
Không hẳn. C chỉ có truyền theo GIÁ TRỊ - nhưng ta truyền giá trị của một ĐỊA CHỈ (con trỏ), đạt hiệu quả tương đương. C++ có tham chiếu thật (int &x). Trong C, con trỏ là cách duy nhất để hàm sửa biến nơi gọi.
scanf phải GHI giá trị đọc được vào x → nó cần ĐỊA CHỈ của x để ghi vào. printf chỉ ĐỌC giá trị x để in → truyền giá trị là đủ. Đây chính là “truyền địa chỉ để hàm sửa biến” bạn đã dùng từ những bài đầu tiên, chỉ là chưa để ý!
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ì sao hàm hoanViSai(int x, int y) không đổi được biến gốc ở nơi gọi?
- 1
Theo dõi swap
Dùng mô phỏng ở Bước 2, dừng ở bước “
*pa = *pb;”. Lúc nàya,b,tbằng bao nhiêu? Vì sao cần biếnt?Hoàn thành khi:
a=7,b=7,t=3. Cầntđể GIỮ giá trị cũ củaatrước khi ghi đè, nếu không sẽ mất nó (bcũng thành 7). - 2
Hàm tăng
Viết
void tang(int *p)làm tăng giá trịptrỏ tới lên 1. Gọitang(&x)vớix = 9thìxthành mấy?Hoàn thành khi:
void tang(int *p) { (*p)++; }→x = 10. Nhớ(*p)++chứ không phải*p++. - 3
Vì sao scanf có &?
Giải thích vì sao
scanf("%d", &tuoi)có&nhưngprintf("%d", tuoi)lại không.Hoàn thành khi:
scanfGHI vàotuoinên cần địa chỉ (&tuoi);printfchỉ ĐỌCtuoinên truyền giá trị là đủ. - 4
Quên dấu &
Khi gọi
hoanVi(a, b)thay vìhoanVi(&a, &b)thì điều gì xảy ra?Hoàn thành khi: Sai kiểu: hàm cần
int*nhưng nhậnint→ trình biên dịch báo lỗi. (Nếu ép kiểu liều,a,bbị hiểu là địa chỉ rác → sập.) - 5
Con trỏ treo
Vì sao hàm sau bị lỗi?
int* tao() { int n = 5; return &n; }Hoàn thành khi:
nlà biến cục bộ, biến mất khitao()kết thúc. Con trỏ trả về trỏ tới vùng nhớ không còn hợp lệ (dangling) → dùng nó là hành vi không xác định. - 6
Trả về hai giá trị
Viết
void chia(int tu, int mau, int *thuong, int *du)để trả CẢ thương và dư qua con trỏ.Hoàn thành khi:
*thuong = tu / mau; *du = tu % mau;- gọi:chia(17, 5, &q, &r);→q=3,r=2.