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ăng | Giải quyết vấn đề gì |
|---|---|
| _Generic | Chọn hàm theo kiểu đối số, lúc dịch. Bài 30.5 đã dùng |
| _Static_assert | Chốt một giả định thành lỗi biên dịch có thông báo của bạn |
| _Alignas và _Alignof | Kiể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 danh | Truy cập trường lồng nhau mà không phải viết tên tầng giữa |
| _Noreturn | Bá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 _s | Bả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ăng | Trước C23 phải làm gì |
|---|---|
| nullptr | NULL, 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ân | 0x0D, 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ộc | Chuẩ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ăng | GCC | Clang | MSVC |
|---|---|---|---|
| C99 đầy đủ | 4.5 | 3.1 | 2019 |
| C11 đầy đủ | 4.9 | 3.6 | 2019, trừ threads.h |
| _Generic | 4.9 | 3.0 | 2019 |
| _Static_assert | 4.6 | 3.0 | 2019 |
| threads.h | Qua glibc 2.28 | Qua libc | Không |
| -std=c2x, một phần C23 | 9 | 9 | Một phần |
| -std=c23 đầy đủ | 14 | 18 | Đang làm |
| typeof chuẩn | 14 | 18 | Chưa |
| #embed | 15 | 19 | Chưa |
Tự làm thử
- Viết macro
TEN_KIEUbằng_Genericvà thử với sáu kiểu. - Cố tình cho hai nhánh
_Genericcùng kiểu và đọc thông báo lỗi. - Thêm năm dòng
_Static_assertvào một dự án của bạn. - Viết bản dự phòng
KIEM_TINHcho C89 và kiểm tra thông báo lỗi của nó. - 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.
- Kiểm tra trình biên dịch của bạn hỗ trợ những gì bằng
__has_includevà__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.hvà VLA đều là tùy chọn. _Genericchọ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_assertbiế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.