Bỏ qua điều hướng, tới nội dung chính
Học C
Bài 51.330 phút đọc

C11 và C23

Sau bài này bạn sẽ làm được

  • Dùng _Generic để chọn hàm theo kiểu lúc dịch
  • Dùng _Static_assert để chốt giả định
  • Kể sáu tính năng mới của C23
  • Biết trình biên dịch nào hỗ trợ tới đâu

C11 thêm những thứ giúp bạn viết thư viện tốt hơn: chọn hàm theo kiểu, khẳng định lúc dịch, kiểm soát căn chỉnh. C23 thì lấy về những tiện nghi mà lập trình viên C đã tự làm bằng macro suốt hai mươi năm.

#Bảy tính năng của C11

Tính năngGiải quyết vấn đề gì
_GenericChọn hàm theo kiểu đối số, lúc dịch. Bài 30.5 đã dùng
_Static_assertChốt một giả định thành lỗi biên dịch có thông báo của bạn
_Alignas và _AlignofKiểm soát căn chỉnh mà không cần mở rộng trình biên dịch
Struct và union nặc danhTruy cập trường lồng nhau mà không phải viết tên tầng giữa
_NoreturnBáo cho trình biên dịch hàm này không bao giờ trả về
threads.h và stdatomic.hĐa luồng chuẩn, thay cho pthread và WinAPI
Annex K, các hàm _sBản an toàn hơn của strcpy và họ hàng

#_Generic

Cú pháp và một ví dụ chạy được
#include <stdio.h>

#define TEN_KIEU(x) _Generic((x), \
    int: "int", long: "long", double: "double", float: "float", \
    char *: "char *", const char *: "const char *", default: "khac")

int main(void) {
    int i = 1; double d = 2.0; char *s = "a";
    printf("%s %s %s %s\n",
           TEN_KIEU(i), TEN_KIEU(d), TEN_KIEU(s), TEN_KIEU(1L));
    return 0;
}
terminal
gcc -std=c11 -O2 -Wall -Wextra -o c11.exe c11.c && ./c11.exe
int double char * long
Ứng dụng thật: một tên hàm cho nhiều kiểu
#include <math.h>
#include <stdlib.h>

#define ABS(x) _Generic((x),          \
    int:         abs,                 \
    long:        labs,                \
    long long:   llabs,               \
    float:       fabsf,               \
    double:      fabs,                \
    long double: fabsl                \
)(x)

/* Bay gio ABS(-3) goi abs, ABS(-3.0f) goi fabsf.
   Do dung la cach tgmath.h duoc cai dat. */

/* Va cho ham cua rieng ban: */

#define IN(x) _Generic((x),                       \
    int:          in_int,                         \
    double:       in_double,                      \
    const char *: in_chuoi,                       \
    char *:       in_chuoi,                       \
    SinhVien *:   in_sinh_vien                    \
)(x)

/* Chu y phai liet ke CA "char *" lan "const char *":
   chung la hai kieu khac nhau, va _Generic khong tu suy dien. */

#_Static_assert

_Static_assert(sizeof(int) == 4, "ma nay gia dinh int 4 byte");
_Static_assert(CHAR_BIT == 8, "ma nay gia dinh byte 8 bit");
_Static_assert(sizeof(void *) == sizeof(uintptr_t), "uintptr_t phai du");

/* Tu C23 thi ten ngan hon va thong bao la tuy chon: */
static_assert(sizeof(int) == 4, "int 4 byte");
static_assert(sizeof(int) == 4);          /* C23: khong can thong bao */

/* Truoc C23, muon dung ten ngan thi: */
#include <assert.h>       /* dinh nghia static_assert thanh _Static_assert */
terminal
# Cố tình cho sai một giả định
gcc -std=c11 -c gd.c
gd.c:10:1: error: static assertion failed: "ma nay gia dinh int it nhat 3200 bit"
 _Static_assert(sizeof(int) >= 400,
 ^~~~~~~~~~~~~~
Bản dự phòng cho C89 và C99
#if __STDC_VERSION__ >= 201112L
  #define KIEM_TINH(dk, tin) _Static_assert(dk, tin)
#else
  #define NOI2(a, b) a##b
  #define NOI(a, b)  NOI2(a, b)
  #define KIEM_TINH(dk, tin) \
      typedef char NOI(kiem_tinh_, __LINE__)[(dk) ? 1 : -1]
#endif

KIEM_TINH(sizeof(int) == 4, "can int 4 byte");

/* Ban gia dua vao mot su that: mang co kich thuoc am la loi dich.

   Thong bao xau hon nhieu:
     error: size of array 'kiem_tinh_10' is negative

   Nhung no chay tu C89, va ten mang chua so dong nen ban tim ra cho. */

#Struct và union nặc danh

c11.c
typedef struct {
    int id;
    struct { int x, y; };     /* nac danh: khong co ten */
} Diem;

int main(void) {
    Diem p;
    p.id = 7;
    p.x = 1;                  /* truy cap THANG, khong phai p.toa_do.x */
    p.y = 2;

    printf("Diem: id=%d x=%d y=%d  sizeof=%d\n",
           p.id, p.x, p.y, (int)sizeof p);
    return 0;
}
terminal
gcc -std=c11 -O2 -Wall -Wextra -o c11.exe c11.c && ./c11.exe
Diem: id=7 x=1 y=2  sizeof=12

#C23

Tính năngTrước C23 phải làm gì
nullptrNULL, có thể là 0 hoặc (void*)0 tùy cài đặt
constexpr int N = 100;enum { N = 100 }; hoặc #define
typeof(x)__typeof__ của GNU, không chuẩn
auto x = 42;Phải viết kiểu ra
bool, true, false là từ khóa#include <stdbool.h>
0b1101 số nhị phân0x0D, hoặc macro tự viết
_BitInt(7)Không có cách nào
#embed "tep.bin"Kịch bản sinh mảng byte lúc dựng
__VA_OPT__Thủ thuật macro rất rối
[[nodiscard]] và họ__attribute__ của GNU
int f() nghĩa là int f(void)Phải viết void tường minh
Bù hai bắt buộcChuẩn cho phép ba cách biểu diễn số âm
Bốn tính năng đáng chú ý nhất
/* 1. constexpr: hang so THAT SU, khong phai macro */

constexpr int TOI_DA = 100;
int a[TOI_DA];                    /* dung lam kich thuoc mang: DUOC */
switch (x) { case TOI_DA: ... }   /* dung lam nhan case: DUOC */

/* Truoc C23, "const int TOI_DA = 100" khong lam duoc hai viec do,
   nhu Bai 32.2 da chi ra. Phai dung enum. */

/* 2. typeof: chuan hoa mot mo rong ai cung dung */

#define DOI_CHO(a, b) do { typeof(a) t = (a); (a) = (b); (b) = t; } while (0)
#define TOI_DA(a, b)  ({ typeof(a) _a = (a), _b = (b); _a > _b ? _a : _b; })

/* 3. #embed: nhung tep nhi phan luc DICH */

static const unsigned char bieu_tuong[] = {
    #embed "bieu_tuong.png"
};
static const char giup[] = {
    #embed "giup.txt" suffix(, 0)     /* them ky tu ket thuc chuoi */
};

/* Truoc C23 phai viet mot kich ban sinh ra mang do, va nho chay
   lai moi khi tep goc doi. */

/* 4. Thuoc tinh chuan */

[[nodiscard]] int phai_dung_ket_qua(void);
[[maybe_unused]] int khong_dung;
[[deprecated("dung ham_moi thay the")]] void ham_cu(void);
[[fallthrough]];
[[noreturn]] void thoat(void);

#Trình biên dịch nào hỗ trợ tới đâu

terminal
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'?
Tính năngGCCClangMSVC
C99 đầy đủ4.53.12019
C11 đầy đủ4.93.62019, trừ threads.h
_Generic4.93.02019
_Static_assert4.63.02019
threads.hQua glibc 2.28Qua libcKhông
-std=c2x, một phần C2399Một phần
-std=c23 đầy đủ1418Đang làm
typeof chuẩn1418Chưa
#embed1519Chưa

Tự làm thử

  1. Viết macro TEN_KIEU bằng _Generic và thử với sáu kiểu.
  2. Cố tình cho hai nhánh _Generic cùng kiểu và đọc thông báo lỗi.
  3. Thêm năm dòng _Static_assert vào một dự án của bạn.
  4. Viết bản dự phòng KIEM_TINH cho C89 và kiểm tra thông báo lỗi của nó.
  5. Viết một union có thẻ dùng struct nặc danh và so với bản có tên tầng giữa.
  6. Kiểm tra trình biên dịch của bạn hỗ trợ những gì bằng __has_include và __has_builtin.

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

  • C11 thêm bảy thứ, trong đó threads.h, stdatomic.h và VLA đều là tùy chọn.
  • _Generic chọn theo kiểu lúc dịch, không tốn gì lúc chạy, nhưng mọi nhánh vẫn phải hợp lệ về cú pháp.
  • _Static_assert biến giả định ngầm thành lỗi biên dịch có thông báo do bạn viết.
  • Struct nặc danh bỏ được tầng tên vô nghĩa, và ứng dụng chính là union có thẻ.
  • C23 chuẩn hóa typeof, constexpr, thuộc tính và bù hai, nhưng tràn số có dấu vẫn là hành vi không xác định.