Bài 51.228 phút đọc
C99, bước nhảy lớn nhất
Sau bài này bạn sẽ làm được
- Dùng được mười tính năng chính của C99
- Viết khởi tạo có chỉ định cho mảng và struct
- Dùng compound literal đúng chỗ
- Biết VLA lợi và hại thế nào
C99 là lần duy nhất C thay đổi nhiều tới mức mã C99 trông khác hẳn mã C89. Mọi thứ bạn viết hàng ngày mà không nghĩ tới, từ khai báo trong vòng for tới uint32_t, đều đến từ bản chuẩn này.
#Mười tính năng
| Tính năng | Trước C99 phải viết thế nào |
|---|---|
| Chú thích một dòng // | Chỉ có /* ... */ |
| Khai báo ở giữa khối | Mọi khai báo phải ở đầu khối |
| for (int i = 0; ...) | int i; ở đầu hàm, và i sống tiếp sau vòng lặp |
| bool, true, false qua stdbool.h | int, hoặc tự #define TRUE 1 |
| long long, ít nhất 64 bit | Không có kiểu chuẩn nào bảo đảm 64 bit |
| inline | Macro, hoặc để trình biên dịch tự quyết |
| Khởi tạo có chỉ định [3] = x | Đếm dấu phẩy bằng tay |
| Compound literal (Point){1,2} | Khai báo một biến tạm có tên |
| restrict | Không có cách nào nói lời hứa đó |
| snprintf | sprintf, và cầu mong bộ đệm đủ lớn |
| stdint.h: uint32_t và họ hàng | Tự typedef theo từng nền tảng |
| __func__ | Tự viết tên hàm vào chuỗi, và quên cập nhật khi đổi tên |
| VLA: int a[n] | malloc, hoặc mảng cố định to quá mức |
| Mảng độ dài biến đổi ở cuối struct | Mở rộng của trình biên dịch, mảng cỡ 0 hoặc cỡ 1 |
#Khai báo ở bất cứ đâu
Khai báo dồn lên đầu
/* C89: moi khai bao o dau khoi */
int xu_ly(const char *ten) {
FILE *f;
char dong[256];
int so_dong;
int ket_qua;
f = fopen(ten, "r");
if (f == NULL) return -1;
so_dong = 0;
while (fgets(dong, sizeof dong, f) != NULL) so_dong++;
fclose(f);
ket_qua = so_dong;
return ket_qua;
}
/* Khoang cach giua khai bao va lan dung dau tien co the la
ba muoi dong. Voi ham dai thi rat de dung nham bien chua khoi tao. */Khai báo ngay chỗ dùng
/* C99: khai bao ngay cho dung */
int xu_ly(const char *ten) {
FILE *f = fopen(ten, "r");
if (f == NULL) return -1;
char dong[256];
int so_dong = 0;
while (fgets(dong, sizeof dong, f) != NULL) ++so_dong;
fclose(f);
return so_dong;
}
/* Moi bien co gia tri ban dau ngay tai cho khai bao.
Khong bien nao ton tai truoc khi co nghia.
Bai 33.3 da noi doc bien chua khoi tao la UB. *//* Bien trong for chi song trong vong lap: */
for (int i = 0; i < n; ++i) { ... }
printf("%d", i); /* error: 'i' undeclared */
/* Neu can gia tri sau vong lap thi khai bao ben ngoai: */
int i;
for (i = 0; i < n; ++i) if (a[i] == can) break;
if (i == n) printf("khong tim thay\n");
/* Va C99 cho khai bao nhieu bien cung kieu: */
for (size_t i = 0, j = n - 1; i < j; ++i, --j) doi_cho(&a[i], &a[j]);#Khởi tạo có chỉ định
Bốn dạng
/* 1. Mang theo chi so */
int a[10] = { [0] = 1, [5] = 50, [9] = 90 };
/* cac phan tu khac deu bang 0 */
/* 2. Struct theo ten truong */
typedef struct { int rong, cao; const char *ten; } CauHinh;
CauHinh c = { .cao = 1080, .rong = 1920, .ten = "chinh" };
/* thu tu khong quan trong, va truong nao khong ghi thi bang 0 */
/* 3. Long nhau */
typedef struct { CauHinh man_hinh; int so; } UngDung;
UngDung u = {
.man_hinh = { .rong = 800, .cao = 600 },
.so = 3,
};
/* 4. Ket hop, va khoang gia tri la mo rong cua GNU */
int bang[256] = {
['a'] = 1, ['b'] = 2,
['0' ... '9'] = 9, /* mo rong GNU, khong phai chuan */
};terminal
gcc -std=c99 -Wall -Wextra -c kt.c
kt.c: In function 'main':
kt.c:8:5: warning: missing initializer for field 'ten' of 'CauHinh' {aka 'struct <anonymous>'} [-Wmissing-field-initializers]
CauHinh c = { 1920, 1080 };
^~~~~~~
kt.c:3:45: note: 'ten' declared here
typedef struct { int rong, cao; const char *ten; } CauHinh;#Compound literal
Một giá trị struct hoặc mảng không cần tên
typedef struct { int x, y; } Diem;
/* Truoc C99: phai co bien tam */
Diem tam = { 1, 2 };
ve_diem(tam);
/* C99: */
ve_diem((Diem){ 1, 2 });
ve_diem((Diem){ .x = 1, .y = 2 });
/* Voi mang: */
tong((int[]){ 1, 2, 3, 4, 5 }, 5);
/* Va gan lai ca struct: */
Diem p = { 0, 0 };
p = (Diem){ 10, 20 }; /* thay cho p.x = 10; p.y = 20; */#stdint.h và inttypes.h
#include <stdint.h>
/* Do rong CHINH XAC, bu hai. Khong bat buoc phai co,
nhung moi may thuc te deu co */
int8_t int16_t int32_t int64_t
uint8_t uint16_t uint32_t uint64_t
/* IT NHAT n bit, chon ban nho nhat. BAT BUOC phai co */
int_least8_t uint_least64_t
/* IT NHAT n bit, chon ban nhanh nhat. BAT BUOC phai co */
int_fast8_t uint_fast32_t
/* Du chua mot con tro */
intptr_t uintptr_t
/* Rong nhat may ho tro */
intmax_t uintmax_t
/* Va cac hang so gioi han */
INT32_MAX UINT64_MAX SIZE_MAX INTPTR_MIN
/* Va macro tao hang so dung kieu */
uint64_t x = UINT64_C(0xFFFFFFFFFFFFFFFF);terminal
# Chạy trên MinGW: size_t là %llu, không phải %lu
gcc -std=c11 -Wall -c dinhdang.c
dinhdang.c: In function 'main':
dinhdang.c:6:15: warning: format '%lu' expects argument of type 'long unsigned int', but argument 2 has type 'size_t' {aka 'long long unsigned int'} [-Wformat=]
printf("%lu\n", n);
~~^ ~
%I64u#VLA: lợi và hại
void xu_ly(size_t n) {
int a[n]; /* VLA: kich thuoc biet luc CHAY */
...
} /* tu giai phong khi ra khoi khoi */
/* Va VLA hai chieu, cho phep viet ma ma tran rat gon: */
void nhan(size_t n, double c[n][n], double a[n][n], double b[n][n]) {
for (size_t i = 0; i < n; ++i)
for (size_t j = 0; j < n; ++j) {
double t = 0;
for (size_t k = 0; k < n; ++k) t += a[i][k] * b[k][j];
c[i][j] = t;
}
}
/* Truoc C99 phai tu tinh chi so: a[i * n + k] */VLA với kích thước không kiểm soát
void xu_ly(const char *dong) {
size_t n = strlen(dong);
char bo_dem[n + 1]; /* n do NGUOI DUNG quyet dinh */
...
}
/* Mot dong dai mot trieu ky tu lam tran ngan xep.
Nhan Linux cam VLA hoan toan tu nam 2018 vi ly do nay. */Bộ đệm nhỏ cố định, tràn thì cấp phát
void xu_ly(const char *dong) {
size_t n = strlen(dong);
char tinh[256];
char *bo_dem = tinh;
if (n + 1 > sizeof tinh) {
bo_dem = malloc(n + 1);
if (bo_dem == NULL) return; /* BAO LOI duoc */
}
...
if (bo_dem != tinh) free(bo_dem);
}
/* Mau nay ten la "bo dem nho tren ngan xep, tran thi ra heap".
Nhanh nhu VLA cho truong hop thuong gap, va an toan cho moi
truong hop. */# Cam VLA hoan toan trong du an:
gcc -Wvla prog.c # canh bao moi VLA
gcc -Werror=vla prog.c # bien thanh loi
# Va kiem tra trinh bien dich co ho tro khong:
#if defined(__STDC_NO_VLA__)
/* C11: trinh bien dich nay KHONG ho tro VLA */
#endif
# Mot phan cua VLA VAN bat buoc trong C11: tham so mang co kich thuoc
# bien doi, tuc "double a[n][n]" trong nguyen mau ham. Chi bien VLA
# tren ngan xep moi la tuy chon.Tự làm thử
- Viết lại một hàm C89 sang phong cách C99 và so số dòng.
- Dùng khởi tạo có chỉ định cho một bảng tra thưa với chỉ số là mã ký tự.
- Bật
-Wmissing-field-initializersvà xác nhận khởi tạo có chỉ định tắt được cảnh báo. - Chứng minh compound literal trong hàm cho ra con trỏ treo còn ở phạm vi tệp thì không.
- In một
uint64_tbằng cảPRIu64lẫn ép kiểu, so hai cách. - Viết hàm dùng mẫu bộ đệm nhỏ cố định tràn thì cấp phát, và so với bản VLA.
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
- C99 là lần thay đổi lớn nhất, và mọi thứ bạn viết hàng ngày đều đến từ nó.
- Khai báo ngay chỗ dùng, kèm giá trị ban đầu, làm biến chưa khởi tạo gần như không thể xảy ra.
- Khởi tạo có chỉ định đặt mọi trường không ghi về 0, và tắt được cảnh báo thiếu trường.
- Compound literal ở phạm vi tệp sống suốt chương trình, còn trong hàm thì chết khi ra khỏi khối.
- VLA có bốn vấn đề nghiêm trọng và là tùy chọn từ C11; nhưng tham số mảng nhiều chiều thì rất đáng dùng.