Nếu bạn đã từng code C một thời gian (hoặc mới hôm qua), có thể bạn đã gặp một tình huống… "tình là tình nhiều khi không mà có, tình là tình nhiều lúc có như không":
Bạn hoàn tất một "công trình thế kỷ". Code nhìn hoàn toàn rõ ràng, rất hợp lý, rất ổn!
Nếu chỉ chạy Compile sẽ thấy không lỗi. Rõ ràng là không có vấn đề gì!
Nhưng nếu bấm Run…
Và… đùng một phát! Bạn gặp lỗi segmentation fault
Bạn thẫn thờ ngồi nhìn màn hình, tự hỏi:
"Ủa mình sai ở đâu vậy trời???"...Bấm Run lại...Vũ Như Cẩn, vẫn gặp lỗi!
Có thể khả năng cao: bạn vừa hội ngộ với một "em đẹp" tên là "con trỏ NULL".
Con trỏ NULL là gì?
Nói đơn giản, dễ hiểu, không màu mè:
Con trỏ NULL là con trỏ không trỏ đến đâu cả. Quá xá là đơn giản phải không?!
Trong C:
int *p = NULL;
Nghĩa là:
p tồn tại
Nhưng nó không trỏ tới bất kỳ vùng nhớ hợp lệ nào
Ví dụ thực tế
Bạn tưởng tượng:
Con trỏ = địa chỉ nhà
NULL = "không có địa chỉ"
Bạn đang cầm một tờ giấy ghi:
"Nhà của tôi ở… (để trống)"
Và bạn vẫn cố...đấm ăn xôi:
- gửi thư
- giao hàng
- đến thăm
Kết quả: ...chẳng có kết quả nào cả! Hư không...!
Lỗi kinh điển
int *p = NULL;
*p = 10; // crashBạn đang ghi dữ liệu vào… "hư không"
Hệ điều hành nhìn thấy và nói:
"Không được phép!"
Và crash ngay lập tức
Vì sao con trỏ NULL nguy hiểm?
Vì nó trông có vẻ hợp lệ, và nó gật gù cho qua nếu bạn dịch code.
Code compile OK
Không warning
Nhìn bằng mắt thường cũng thấy "có vẻ đúng"
Nhưng runtime thì… "tai biến"
5 tình huống phổ biến gây ra NULL pointer
1. Quên cấp phát bộ nhớ
int *p;
*p = 5; // crashp chưa trỏ tới đâu cả
2. malloc thất bại (ít gặp nhưng rất nguy hiểm)
int *p = malloc(sizeof(int));
if (p == NULL) {
// hết bộ nhớ
}Nếu bạn không check --> rất dễ crash
3. Hàm trả về NULL nhưng không kiểm tra
int *getData() {
return NULL;
}
int *p = getData();
*p = 10; // crashTin tưởng mù quáng là một cái giá đắt
4. Sau khi free mà quên set NULL
int *p = malloc(sizeof(int));
free(p);
*p = 10; // crashĐây gọi là "dangling pointer" (không hẳn NULL nhưng nguy hiểm tương tự)
Best practice:
free(p);
p = NULL;5. Truyền pointer sai giữa các hàm
Đây là kiểu lỗi "cao cấp hơn một chút" nhưng rất hay gặp khi code lớn
Cách tránh NULL pointer
1. Luôn kiểm tra trước khi dùng
if (p != NULL) {
*p = 10;
}2. Sau khi free --> set NULL
free(p);
p = NULL;Tránh "xài lại đồ cũ"
3. Khi dùng malloc --> luôn check
int *p = malloc(sizeof(int));
if (p == NULL) {
printf("Hết bộ nhớ rồi!\n");
}Để hiểu rõ hơn, bạn nên thử chạy code và "mục sở thị"
Bạn copy đoạn này vào: Online C Compiler
#include <stdio.h>
int main() {
int *p = NULL;
printf("Trước khi crash\n");
*p = 10; // crash
printf("Sau khi crash\n");
return 0;
}Kết quả:
Chỉ in ra đoạn Text: "Trước khi crash"
Và tiếp theo là Program finished with exit code 139
Điều đó có nghĩa là đoạn code "*p = 10; " đã "có vấn đề"
Sửa lại đoạn code như sau:
#include <stdio.h>
int main() {
int *p = NULL;
printf("Trước khi crash\n");
printf("p = %p\n", p);
*p = 10; // crash
printf("Sau khi crash\n");
return 0;
}Nếu thấy:
p = (nil)
Bingo — Bạn đã tìn ra lỗi NULL pointer
Một sự thật biết rồi, khổ lắm, nói mãi
Rất nhiều bug khó chịu trong C đến từ:
- pointer
- NULL
- memory
Nếu bạn thấy rối:
- Bạn không cô đơn
Tổng kết
Con trỏ NULL không phải là "lỗi khó"
Nó chỉ là một cái bẫy rất dễ dính
Chỉ cần nhớ:
NULL = không trỏ đi đâu
Dùng NULL = crash
Luôn kiểm tra trước khi dùng
FAQ nhanh
NULL pointer có luôn gây segmentation fault không?
Gần như luôn luôn nếu bạn dereference (*p)
Làm sao biết pointer có NULL không?
In ra hoặc check if (p == NULL)
Có nên tránh dùng pointer không?
Không. Nhưng phải dùng cẩn thận
Bạn có thể cũng đang gặp:
Segmentation fault trong C là gì?
Memory leak nguy hiểm thế nào?
Stack vs Heap khác nhau ra sao?
Segmentation fault trong C là gì?
Memory leak nguy hiểm thế nào?
Stack vs Heap khác nhau ra sao?