Bài 51.124 phút đọc
Lịch sử các chuẩn C
Sau bài này bạn sẽ làm được
- Kể tên các bản chuẩn và năm ra đời
- Nêu tính năng đáng nhớ nhất của từng bản
- Chọn được -std phù hợp cho một dự án
- Biết C17 không thêm tính năng nào
C ra đời năm 1972 và được chuẩn hóa lần đầu năm 1989. Trong hơn ba mươi năm kể từ đó, chuẩn thay đổi đúng bốn lần đáng kể. Sự chậm chạp đó không phải nhược điểm, nó là lý do mã C viết năm 1990 vẫn dịch được hôm nay.
#Bảy bản chuẩn
| Chuẩn | Năm | Tính năng đáng nhớ |
|---|---|---|
| K&R C | 1978 | Bản gốc trong cuốn sách của Kernighan và Ritchie, chưa chuẩn hóa |
| C89 và C90 | 1989 | Chuẩn hóa đầu tiên. Nguyên mẫu hàm, const, volatile, void *, thư viện chuẩn |
| C95 | 1995 | Bản sửa đổi nhỏ: wchar.h, iso646.h, digraph |
| C99 | 1999 | Bước nhảy lớn nhất. Xem Bài 51.2 |
| C11 | 2011 | _Generic, _Static_assert, _Alignas, threads.h, stdatomic.h |
| C17 và C18 | 2018 | CHỈ sửa lỗi biên tập, không một tính năng mới nào |
| C23 | 2024 | nullptr, constexpr, typeof, auto, bool là từ khóa, #embed, bù hai bắt buộc |
#Vì sao C thay đổi chậm
/* Vi du ve toc do chap nhan trong thuc te:
C99 ra doi 1999.
Microsoft Visual C++ ho tro day du C99 vao nam 2019. Hai muoi nam.
Ly do: MSVC tap trung vao C++, va C++ khong lay het C99.
Nhieu du an Windows vi vay ket o C89 rat lau.
Nen "chuan noi gi" va "toi dung duoc gi" la hai cau hoi khac nhau.
Cau thu hai phu thuoc vao trinh bien dich toi thieu ma du an cua ban
phai ho tro. */#Chọn -std nào
| Cờ | Khi nào chọn |
|---|---|
| -std=c89 | Mã phải dịch được bằng trình biên dịch rất cũ, hoặc cho vi điều khiển lạ |
| -std=c99 | Mã nhúng, hoặc dự án phải chạy trên MSVC cũ |
| -std=c11 | Mặc định tốt cho dự án mới trong nhiều năm qua |
| -std=c17 | Giống c11 nhưng đã sửa lỗi biên tập. Lựa chọn an toàn nhất hiện nay |
| -std=c23 | Cần GCC 14 hoặc Clang 18 trở lên. Dùng khi bạn kiểm soát được toàn bộ chuỗi công cụ |
| -std=gnu17 | Mặc định của GCC. C17 cộng các mở rộng của GNU |
# Ba muc nghiem ngat, tu long toi chat
gcc -std=c17 prog.c # chuan, nhung mo rong GNU van duoc
# chap nhan mot cach im lang
gcc -std=c17 -pedantic prog.c # CANH BAO khi dung mo rong
gcc -std=c17 -pedantic-errors prog.c # LOI khi dung mo rong <- nen dung
# neu ban muon ma that su kha chuyenterminal
# Một mở rộng của GNU, dịch được ở gnu17, bị từ chối ở c17 nghiêm ngặt
gcc -std=c17 -pedantic-errors -c mo_rong.c
mo_rong.c:4:13: error: ISO C forbids braced-groups within expressions [-Wpedantic]
int x = ({ int t = 5; t * 2; });
^#Kiểm tra trình biên dịch hỗ trợ tới đâu
Macro __STDC_VERSION__
#include <stdio.h>
int main(void) {
#if !defined(__STDC_VERSION__)
puts("C89 hoac K&R");
#elif __STDC_VERSION__ == 199409L
puts("C95");
#elif __STDC_VERSION__ == 199901L
puts("C99");
#elif __STDC_VERSION__ == 201112L
puts("C11");
#elif __STDC_VERSION__ == 201710L
puts("C17");
#elif __STDC_VERSION__ >= 202311L
puts("C23 hoac moi hon");
#else
printf("khong ro: %ld\n", __STDC_VERSION__);
#endif
return 0;
}# Bon lenh de biet trinh bien dich cua ban lam duoc gi
gcc --version # phien ban
gcc -dM -E - < /dev/null | grep STDC # moi macro chuan da dinh nghia
gcc -dM -E - < /dev/null | wc -l # tong so macro dinh nghia san
echo | gcc -xc -E -v - # duong dan tim header
# Va mot bai kiem tra cu the:
echo 'int main(void){ return _Generic(1, int:0, default:1); }' \
| gcc -std=c11 -xc - -o /dev/null && echo "co _Generic"terminal
# Trên GCC 8.1 của MinGW dùng trong giáo trình này
gcc --version | head -1
gcc.exe (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 8.1.0
gcc -std=c23 -c t.c
gcc.exe: error: unrecognized command line option '-std=c23'; did you mean '-std=c11'?
gcc -std=c2x -c t.c
gcc.exe: error: unrecognized command line option '-std=c2x'; did you mean '-std=c1x'?
#gnu17 và các mở rộng
| Mở rộng GNU | Làm gì | Chuẩn hóa chưa |
|---|---|---|
| typeof(x) | Lấy kiểu của một biểu thức | Có, từ C23 |
| ({ ... }) | Biểu thức khối, trả về giá trị câu cuối | Chưa |
| __attribute__((...)) | Thuộc tính hàm và biến | Một phần, C23 có [[...]] |
| Nhãn làm giá trị, &&nhan | Bảng nhảy tự viết | Chưa |
| Mảng độ dài 0 | struct có phần đuôi biến đổi | Có, C99 dùng cú pháp [] |
| __builtin_expect | Gợi ý nhánh nào hay xảy ra | Một phần, C23 có [[likely]] |
| Hàm lồng nhau | Định nghĩa hàm trong hàm | Chưa, và có lẽ không bao giờ |
| case 1 ... 5 | Khoảng giá trị trong switch | Chưa |
Ba mở rộng đáng dùng nhất, và cách viết an toàn
/* 1. typeof: rat huu ich cho macro */
#if defined(__GNUC__)
#define DOI_CHO(a, b) do { \
__typeof__(a) _t = (a); (a) = (b); (b) = _t; } while (0)
#endif
/* C23 chuan hoa thanh "typeof" khong gach duoi.
Dung "__typeof__" thi chay ca o -std=c17 nghiem ngat, vi ten
bat dau bang hai gach duoi thuoc ve trinh bien dich. */
/* 2. __attribute__: dung qua mot macro de tat duoc */
#if defined(__GNUC__)
#define KHONG_TRA_VE __attribute__((noreturn))
#define IN_KIEU(a, b) __attribute__((format(printf, a, b)))
#else
#define KHONG_TRA_VE
#define IN_KIEU(a, b)
#endif
KHONG_TRA_VE void thoat_voi_loi(const char *tin);
IN_KIEU(1, 2) void ghi_log(const char *dinh_dang, ...);
/* format(printf, 1, 2) rat dang gia: trinh bien dich kiem tra
dac ta dinh dang cua HAM CUA BAN nhu voi printf. */
/* 3. Mang do dai bien doi o cuoi struct, da chuan hoa o C99 */
struct Goi {
size_t n;
char du_lieu[]; /* C99: dung [] */
/* char du_lieu[0]; mo rong GNU cu, dung [] thay */
};Tự làm thử
- In
__STDC_VERSION__với năm giá trị-stdkhác nhau. - Viết mã dùng biểu thức khối và xác nhận
-pedantic-errorstừ chối nó. - Đếm số macro
gcc -dM -Eđịnh nghĩa sẵn trên máy bạn. - Viết macro
KIEM_TRA_TINHchạy được cả ở C89 lẫn C11. - Bọc ba mở rộng của GNU trong macro có bản dự phòng.
- Dùng
__has_includeđể bật một tính năng tùy chọn.
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
- Thực chất chỉ có năm bản khác nhau về nội dung: C89, C95, C99, C11, C23. C90 giống C89, C17 giống C11 đã sửa lỗi biên tập.
- C thay đổi chậm một cách cố ý, vì tương thích ngược và vì nó phải chạy trên mọi máy.
- Không ghi
-stdnghĩa là bạn đang dùnggnu17, và mã của bạn có thể phụ thuộc vào mở rộng GNU mà không biết. -std=c17là lựa chọn an toàn nhất hiện nay;-std=c23cần GCC 14 hoặc Clang 18.- Dùng mở rộng thì bọc trong
#if defined(__GNUC__), đặt sau macro của riêng bạn, và ưu tiên tên có hai gạch dưới.