Bài 5 · Nâng cao · 22 phút· Cập nhật 11/06/2026
Chuỗi & con trỏ char
Biên soạn bởi Nguyễn Anh Tuấn
Chuỗi và con trỏ char trong C (string & char pointer): char arr[] vs char *p, chuỗi hằng chỉ-đọc, duyệt chuỗi bằng con trỏ và tự viết strlen.
Nhắc lại từ khoá cơ bản: chuỗi là mảng char kết thúc bằng \0. Ghép với bài trước: tên mảng là địa chỉ phần tử đầu, nên một char * có thể trỏ thẳng vào chuỗi:
chuoi-con-tro.c
#include <stdio.h>
int main() {
char s[] = "Hi"; // {'H','i','\0'}
char *p = s; // p tro toi 'H' (s[0])
printf("%c\n", *p); // H - ky tu dau
printf("%s\n", p); // Hi - in tu p toi '\0'
return 0;
} Kết quả khi chạy
H Hi
- ▸Chuỗi = mảng char + ký tự kết thúc \0.
- ▸char *p = s; cho p trỏ tới ký tự đầu (không cần &).
- ▸%c in một ký tự (*p); %s in cả chuỗi từ p tới \0.
Đây là khác biệt quan trọng nhất của bài. Mảng tạo một bản sao sửa được; con trỏ tới chuỗi trong nháy kép trỏ vào vùng chỉ-đọc:
array-vs-literal.c
#include <stdio.h>
int main() {
char arr[] = "Hello"; // BAN SAO, sua duoc (tren stack)
char *lit = "Hello"; // tro toi chuoi HANG (read-only)
arr[0] = 'J'; // OK -> "Jello"
// lit[0] = 'J'; // UB! sua chuoi hang -> co the sap
printf("%s\n", arr); // Jello
return 0;
} Kết quả khi chạy
Jello
Quy tắc
Vì chuỗi kết thúc bằng \0, ta duyệt nó bằng cách cho con trỏ chạy p++ cho tới khi *p == '\0'. Bấm Bước (p++) để xem con trỏ đi và đếm độ dài:
while (*p != '\0') { n++; p++; }
char * nhảy 1 byte mỗi bước (sizeof char), nên duyệt chuỗi chỉ là p++ cho tới khi *p bằng '\0'.
do-dai.c
#include <stdio.h>
int doDai(char *p) {
int n = 0;
while (*p != '\0') { // chua gap ky tu ket thuc
n++;
p++; // tien toi ky tu ke (1 byte)
}
return n;
}
int main() {
printf("%d\n", doDai("Hello")); // 5
return 0;
} Kết quả khi chạy
5
Hiểu cơ chế trên thì tự viết được những hàm chuỗi quen thuộc. Chép một chuỗi là duyệt src tới \0, chép từng ký tự, rồi tự thêm \0 ở cuối:
chep.c
#include <stdio.h>
void chep(char *dst, char *src) {
while (*src != '\0') {
*dst = *src; // chep ky tu
dst++;
src++;
}
*dst = '\0'; // DUNG QUEN: dau ket thuc chuoi
}
int main() {
char buf[10];
chep(buf, "Hi");
printf("%s\n", buf); // Hi
return 0;
} Kết quả khi chạy
Hi
- ▸char* duyệt chuỗi: p++ nhảy 1 byte mỗi bước (sizeof char).
- ▸Thành ngữ c = *p++; đọc *p rồi tiến p - rất hay gặp trong code C.
- ▸Tự dựng chuỗi thì PHẢI tự đặt \0 ở cuối, nếu không chuỗi sẽ không có điểm dừng.
Ba lỗi chuỗi-con-trỏ nguy hiểm nhất:
- ▸Sửa string literal: char *p = "abc"; p[0] = 'X'; → hành vi không xác định.
- ▸Quên \0 khi tự dựng chuỗi → printf/strlen đọc tràn sang vùng nhớ lạ.
- ▸char *p chưa trỏ tới buffer hợp lệ rồi ghi vào → con trỏ hoang, hỏng bộ nhớ.
An toàn bộ đệm
Câu hỏi thường gặp
arr là một MẢNG: trình biên dịch sao chép "Hi\0" vào vùng nhớ của arr (thường trên stack), bạn sửa thoải mái. p là CON TRỎ trỏ tới một chuỗi HẰNG nằm ở vùng chỉ-đọc; sửa qua p (p[0] = ...) là hành vi không xác định, thường gây sập.
Nó in từng ký tự kể từ địa chỉ p cho tới khi gặp \0. Không có \0 thì printf cứ đọc tràn sang vùng nhớ kế tiếp - in ra rác và có thể sập. Đó là lý do \0 sống còn.
1 byte, vì sizeof(char) = 1. Nhờ vậy duyệt chuỗi rất gọn: chỉ p++ để sang ký tự kế. (So với int* nhảy 4 byte - xem bài Con trỏ & mảng.)
Là *(p++): lấy giá trị *p TRƯỚC, rồi tăng p. Một thành ngữ C kinh điển để vừa đọc ký tự vừa tiến con trỏ: c = *p++;. Đừng nhầm với (*p)++ (tăng giá trị được trỏ tới).
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.
char arr[] = "Hi"; và char *p = "Hi"; khác nhau căn bản ở chỗ nào?
- 1
Đếm bằng con trỏ
Dùng công cụ ở Bước 3, gõ "Code" rồi bấm
p++tới hết. Độ dài là bao nhiêu? Con trỏ dừng ở ô có giá trị gì?Hoàn thành khi: Độ dài 4. Con trỏ dừng ở ô
\0(mã ASCII 0) - vòngwhiledừng khi*p == \0. - 2
Ký tự đầu
Cho
char s[] = "abc"; char *p = s;thì*pbằng ký tự nào? Mã ASCII bao nhiêu?Hoàn thành khi:
*p = 'a'(mã 97) -ptrỏ tới phần tử đầus[0]. - 3
Bước nhảy của char*
Mỗi
p++vớichar*nhảy bao nhiêu byte? Vì sao khácint*?Hoàn thành khi: 1 byte (
sizeof char= 1).int*nhảy 4 vìsizeof int= 4. - 4
Tự viết strlen
Viết hàm
int doDai(char *p)đếm độ dài chuỗi bằng con trỏ, KHÔNG dùngstrlen.Hoàn thành khi:
int n = 0; while (*p != '\0') { n++; p++; } return n; - 5
Sửa được hay không?
char arr[] = "Hi"; arr[0] = 'J';có hợp lệ? Cònchar *p = "Hi"; p[0] = 'J';thì sao?Hoàn thành khi:
arrOK (bản sao, sửa được) → "Ji".plà chuỗi hằng read-only ⇒ hành vi không xác định (thường sập). - 6
Vì sao cần \0
Khi tự chép chuỗi qua con trỏ, vì sao bắt buộc thêm
\0ở cuối đích?Hoàn thành khi: Không có
\0,printf/strlenkhông biết chuỗi kết thúc ở đâu → đọc tràn sang vùng nhớ kế tiếp.