Bỏ qua điều hướng, tới nội dung chính
Học C
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ẩnNămTính năng đáng nhớ
K&R C1978Bản gốc trong cuốn sách của Kernighan và Ritchie, chưa chuẩn hóa
C89 và C901989Chuẩn hóa đầu tiên. Nguyên mẫu hàm, const, volatile, void *, thư viện chuẩn
C951995Bản sửa đổi nhỏ: wchar.h, iso646.h, digraph
C991999Bước nhảy lớn nhất. Xem Bài 51.2
C112011_Generic, _Static_assert, _Alignas, threads.h, stdatomic.h
C17 và C182018CHỈ sửa lỗi biên tập, không một tính năng mới nào
C232024nullptr, 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=c89Mã 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=c99Mã nhúng, hoặc dự án phải chạy trên MSVC cũ
-std=c11Mặc định tốt cho dự án mới trong nhiều năm qua
-std=c17Giố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=c23Cầ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=gnu17Mặ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 chuyen
terminal
# 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 GNULàm gìChuẩn hóa chưa
typeof(x)Lấy kiểu của một biểu thứcCó, từ C23
({ ... })Biểu thức khối, trả về giá trị câu cuốiChưa
__attribute__((...))Thuộc tính hàm và biếnMột phần, C23 có [[...]]
Nhãn làm giá trị, &&nhanBảng nhảy tự viếtChưa
Mảng độ dài 0struct có phần đuôi biến đổiCó, C99 dùng cú pháp []
__builtin_expectGợi ý nhánh nào hay xảy raMột phần, C23 có [[likely]]
Hàm lồng nhauĐịnh nghĩa hàm trong hàmChưa, và có lẽ không bao giờ
case 1 ... 5Khoảng giá trị trong switchChư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ử

  1. In __STDC_VERSION__ với năm giá trị -std khác nhau.
  2. Viết mã dùng biểu thức khối và xác nhận -pedantic-errors từ chối nó.
  3. Đếm số macro gcc -dM -E định nghĩa sẵn trên máy bạn.
  4. Viết macro KIEM_TRA_TINH chạy được cả ở C89 lẫn C11.
  5. Bọc ba mở rộng của GNU trong macro có bản dự phòng.
  6. 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 -std nghĩa là bạn đang dùng gnu17, và mã của bạn có thể phụ thuộc vào mở rộng GNU mà không biết.
  • -std=c17 là lựa chọn an toàn nhất hiện nay; -std=c23 cầ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.