Bỏ qua điều hướng, tới nội dung chính
Học C
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ăngTrước C99 phải viết thế nào
Chú thích một dòng //Chỉ có /* ... */
Khai báo ở giữa khốiMọ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.hint, hoặc tự #define TRUE 1
long long, ít nhất 64 bitKhông có kiểu chuẩn nào bảo đảm 64 bit
inlineMacro, 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
restrictKhông có cách nào nói lời hứa đó
snprintfsprintf, và cầu mong bộ đệm đủ lớn
stdint.h: uint32_t và họ hàngTự 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 structMở 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ử

  1. Viết lại một hàm C89 sang phong cách C99 và so số dòng.
  2. 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ự.
  3. Bật -Wmissing-field-initializers và xác nhận khởi tạo có chỉ định tắt được cảnh báo.
  4. Chứng minh compound literal trong hàm cho ra con trỏ treo còn ở phạm vi tệp thì không.
  5. In một uint64_t bằng cả PRIu64 lẫn ép kiểu, so hai cách.
  6. 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.