Bài 56.430 phút đọc
Xử lý lỗi
Sau bài này bạn sẽ làm được
- So sánh bốn chiến lược trả lỗi
- Viết hàm dọn dẹp bằng goto một cách đúng
- Truyền lỗi lên trên mà không mất thông tin
- Biết khi nào dừng chương trình là đúng
C không có ngoại lệ, nên xử lý lỗi là một quyết định thiết kế bạn phải đưa ra một cách có ý thức. Bài này so sánh bốn chiến lược bằng cách chạy cả bốn trên cùng một dữ liệu, và hai trong số đó cho ra kết quả sai trong im lặng.
#Bốn chiến lược
| Chiến lược | Hình thức | Ví dụ trong thư viện chuẩn |
|---|---|---|
| Mã lỗi trả về, kết quả qua tham số ra | int f(const char *s, int *ra) | Không có, nhưng là kiểu của nhiều thư viện hiện đại |
| Giá trị canh gác | int f(const char *s), trả về INT_MIN nếu lỗi | atoi, strchr trả về NULL, malloc trả về NULL |
| Biến lỗi toàn cục | Trả về giá trị, đặt errno | strtol, fopen, mọi lời gọi hệ thống POSIX |
| Cấu trúc lỗi giàu thông tin | int f(const char *s, int *ra, Loi *l) | SQLite với sqlite3_errmsg, libcurl với CURLcode |
Cách 1: mã lỗi và tham số ra
enum { OK = 0, LOI_NULL = -1, LOI_DINH_DANG = -2, LOI_TRAN = -3 };
static const char *ten_loi(int ma) {
switch (ma) {
case OK: return "OK";
case LOI_NULL: return "doi so NULL";
case LOI_DINH_DANG: return "khong phai so";
case LOI_TRAN: return "tran pham vi int";
default: return "khong biet";
}
}
static int doc_so_1(const char *s, int *ra) {
if (s == NULL || ra == NULL) return LOI_NULL;
errno = 0;
char *cuoi;
long v = strtol(s, &cuoi, 10);
if (cuoi == s || *cuoi != '\0') return LOI_DINH_DANG;
if (errno == ERANGE || v < INT_MIN || v > INT_MAX) return LOI_TRAN;
*ra = (int)v;
return OK;
}Cách 2 và cách 3: canh gác, và biến toàn cục
/* Cach 2: gia tri canh gac */
static int doc_so_2(const char *s) {
char *cuoi;
long v = strtol(s, &cuoi, 10);
if (cuoi == s || *cuoi != '\0') return INT_MIN; /* canh gac */
return (int)v;
}
/* Cach 3: bien loi toan cuc kieu errno */
static int loi_cuoi = 0;
static int doc_so_3(const char *s) {
loi_cuoi = 0;
char *cuoi;
long v = strtol(s, &cuoi, 10);
if (cuoi == s || *cuoi != '\0') { loi_cuoi = LOI_DINH_DANG; return 0; }
return (int)v;
}Cách 4: cấu trúc lỗi
typedef struct {
int ma;
char thong_diep[128];
const char *tep;
int dong;
} Loi;
#define DAT_LOI(l, m, ...) \
do { (l)->ma = (m); (l)->tep = __FILE__; (l)->dong = __LINE__; \
snprintf((l)->thong_diep, sizeof (l)->thong_diep, \
__VA_ARGS__); } while (0)
static int doc_so_4(const char *s, int *ra, Loi *l) {
char *cuoi;
errno = 0;
long v = strtol(s, &cuoi, 10);
if (cuoi == s || *cuoi != '\0') {
DAT_LOI(l, LOI_DINH_DANG, "\"%s\" khong phai so nguyen", s);
return -1;
}
if (errno == ERANGE || v < INT_MIN || v > INT_MAX) {
DAT_LOI(l, LOI_TRAN, "\"%s\" vuot pham vi int (%d..%d)",
s, INT_MIN, INT_MAX);
return -1;
}
*ra = (int)v;
return 0;
}#Chạy thử cả bốn
terminal
gcc -std=c11 -O2 -Wall -Wextra -D__USE_MINGW_ANSI_STDIO=1 bon.c -o bon.exe
./bon.exe
dau vao: "42" cach 1: ma=0 (OK) gia tri=42 cach 2: 42 cach 3: 42, loi_cuoi=0 cach 4: 42 dau vao: "abc" cach 1: ma=-2 (khong phai so) cach 2: -2147483648 (canh gac hay gia tri that?) cach 3: 0, loi_cuoi=-2 cach 4: "abc" khong phai so nguyen [bon.c:67] dau vao: "99999999999999" cach 1: ma=-3 (tran pham vi int) cach 2: 2147483647 cach 3: 2147483647, loi_cuoi=0 cach 4: "99999999999999" vuot pham vi int (-2147483648..2147483647) [bon.c:71] dau vao: "" cach 1: ma=-2 (khong phai so) cach 2: -2147483648 (canh gac hay gia tri that?) cach 3: 0, loi_cuoi=-2 cach 4: "" khong phai so nguyen [bon.c:67]
#Dọn dẹp bằng goto
dondep.c, bốn tài nguyên
static int lam_viec(void) {
int ma = -1;
char *a = NULL, *b = NULL, *c = NULL, *d = NULL;
a = cap(16, "a"); if (!a) goto het;
b = cap(16, "b"); if (!b) goto tha_a;
c = cap(16, "c"); if (!c) goto tha_b;
d = cap(16, "d"); if (!d) goto tha_c;
printf(" lam viec that\n");
ma = 0;
tha(d, "d"); /* duong ra binh thuong roi vao chuoi nhan */
tha_c:
tha(c, "c");
tha_b:
tha(b, "b");
tha_a:
tha(a, "a");
het:
return ma;
}terminal
./dd.exe
=== buoc hong = 0 === a: cap b: cap c: cap d: cap lam viec that d: tha c: tha b: tha a: tha ket qua=0, con lai 0 vung chua tha === buoc hong = 2 === a: cap b: CO Y that bai a: tha ket qua=-1, con lai 0 vung chua tha === buoc hong = 4 === a: cap b: cap c: cap d: CO Y that bai c: tha b: tha a: tha ket qua=-1, con lai 0 vung chua tha
Không dùng goto
int lam_viec(void) {
char *a = malloc(16);
if (!a) return -1;
char *b = malloc(16);
if (!b) { free(a); return -1; }
char *c = malloc(16);
if (!c) { free(b); free(a); return -1; }
char *d = malloc(16);
if (!d) { free(c); free(b); free(a); return -1; }
/* Voi 4 tai nguyen: 1+2+3 = 6 lenh free trong nhanh loi.
Voi 8 tai nguyen: 28 lenh free.
Them mot tai nguyen o giua = sua moi nhanh ben duoi.
Day la noi ro ri bo nho sinh ra. */
...
}
Dùng goto
int lam_viec(void) {
int ma = -1;
char *a = NULL, *b = NULL, *c = NULL, *d = NULL;
a = malloc(16); if (!a) goto het;
b = malloc(16); if (!b) goto tha_a;
c = malloc(16); if (!c) goto tha_b;
d = malloc(16); if (!d) goto tha_c;
ma = 0;
free(d);
tha_c: free(c);
tha_b: free(b);
tha_a: free(a);
het: return ma;
}
/* Them mot tai nguyen = them mot dong va mot nhan.
Day la mau chuan cua nhan Linux, cua OpenSSL,
va cua gan nhu moi ma C nghiem tuc. */
#Truyền lỗi lên trên
Ba tầng, và thông tin mất dần
/* Tang thap */
int tep_doc(const char *dd, char **ra, size_t *co) {
FILE *t = fopen(dd, "rb");
if (!t) return -1; /* mat: LY DO khong mo duoc */
...
}
/* Tang giua */
int cau_hinh_nap(const char *dd, CauHinh *c) {
char *noi_dung; size_t co;
if (tep_doc(dd, &noi_dung, &co) != 0) return -1; /* mat: TEP NAO */
...
}
/* Tang cao */
int khoi_dong(void) {
CauHinh c;
if (cau_hinh_nap("/etc/ct.conf", &c) != 0) {
fprintf(stderr, "khong khoi dong duoc\n"); /* vo dung */
return 1;
}
}
/* Nguoi dung nhan duoc: "khong khoi dong duoc".
Nguyen nhan that: khong co quyen doc /etc/ct.conf.
Ho khong the tu sua. */#Khi nào dừng chương trình
| Tình huống | Xử lý hay dừng | Vì sao |
|---|---|---|
| Tệp người dùng chỉ định không tồn tại | Xử lý | Người dùng gõ nhầm, báo và cho thử lại |
| Mạng ngắt giữa chừng | Xử lý | Bình thường, thử lại hoặc báo |
| Dữ liệu vào sai định dạng | Xử lý | Đầu vào không tin được là chuyện thường |
| malloc trả về NULL | Tuỳ | Xem bên dưới |
| Bất biến nội bộ bị vi phạm | Dừng | Chương trình đã ở trạng thái không hiểu được |
| Đối số NULL cho hàm nội bộ | Dừng, bằng assert | Đó là lỗi lập trình, không phải lỗi lúc chạy |
| Chỉ số vượt mảng trong mã của bạn | Dừng | Tiếp tục là hỏng bộ nhớ |
Tự làm thử
- Chạy
bon.cvà giải thích vì sao cách 3 báoloi_cuoi=0cho đầu vào tràn. - Sửa
doc_so_2vàdoc_so_3để chúng phát hiện được tràn. - Viết lại một hàm của bạn có ba
returnlỗi thành mẫugoto. - Thêm
warn_unused_resultvào các hàm trả mã lỗi và đếm số cảnh báo mới. - Cài cấu trúc
Loicó chuỗi nguyên nhân ba tầng và in ra đầy đủ. - Tìm trong dự án của bạn một chỗ ghi nhật ký ở nhiều tầng cho cùng một lỗi, và dồn về một chỗ.
Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.
Tóm tắt
- Bốn chiến lược; mặc định dùng mã lỗi trả về cộng tham số ra.
- Giá trị canh gác chỉ dùng được khi miền giá trị có vùng chắc chắn không hợp lệ.
- Biến lỗi toàn cục phải được xoá trước khi gọi và lưu lại ngay sau khi lỗi.
gotodọn dẹp: nhảy về phía trước, khởi tạo NULL, tên nhãn nói việc, thả ngược thứ tự cấp.- Xử lý hoặc truyền lên, không làm cả hai; ghi nhật ký một lần ở tầng biết phải làm gì tiếp.