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

Đọc chuẩn và viết mã khả chuyển

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

  • Tìm và tra được một mục trong bản nháp chuẩn
  • Biết Annex J liệt kê gì
  • Kể bảy giả định sai làm mã không khả chuyển
  • Kiểm chứng khả chuyển bằng nhiều trình biên dịch

Bản chuẩn chính thức của ISO tốn vài trăm đô la. Bản nháp cuối cùng trước khi xuất bản thì miễn phí và khác bản chính thức đúng vài lỗi chính tả. Đó là tài liệu bạn nên có sẵn.

#Bản nháp miễn phí

Mã bản nhápChuẩnGhi chú
N1256C99Đã gộp ba bản đính chính
N1570C11Bản hay được trích dẫn nhất, dễ tìm nhất
N2176C17Ít khác N1570
N3220C23Bản nháp gần cuối

#Cấu trúc tài liệu

MụcNội dungBạn sẽ mở nó khi
1 tới 4Phạm vi, thuật ngữ, mức tuân thủHiếm
5Môi trường: khởi động chương trình, giới hạn dịchTìm giới hạn tối thiểu chuẩn bảo đảm
6.2Khái niệm: phạm vi, liên kết, thời gian lưu trữ, kiểuBài 52.1 và 52.3 dùng mục này
6.3Chuyển đổi kiểuKhi một phép tính cho kết quả lạ
6.5Biểu thức, độ ưu tiên, điểm tuần tựBài 33.5 và 33.6 dùng mục này
6.7Khai báo, bao gồm bố cục structBài 53.1 dùng mục này
6.8Câu lệnhHiếm
6.10Bộ tiền xử lýChương 54 dùng mục này
7Thư viện chuẩn. 7.21 stdio, 7.22 stdlib, 7.24 stringRất thường xuyên
Phụ lục ATóm tắt cú pháp, dạng BNFKhi viết trình phân tích
Phụ lục JDanh sách đầy đủ UB, unspecified, implementation-definedBài 33.1 tới 33.7 dùng phụ lục này

#Cách đọc một mục

Một mục thật, 6.5.6 đoạn 8, về số học con trỏ
When an expression that has integer type is added to or subtracted
from a pointer, the result has the type of the pointer operand. [...]
if the expression P points to the i-th element of an array object,
the expressions (P)+N (equivalently, N+(P)) and (P)-N (where N has
the value n) point to, respectively, the i+n-th and i-n-th elements
of the array object, provided they exist. Moreover, if the expression
P points to the last element of an array object, the expression (P)+1
points one past the last element of the array object, and if the
expression Q points one past the last element of an array object,
the expression (Q)-1 points to the last element of the array object.
If both the pointer operand and the result point one past the last
element of the array object, the evaluation shall not produce an
overflow; otherwise, the behavior is undefined.

#Bảy giả định sai

Bảy giả định hay gặp
sizeof(int) == 4           /* co the la 2 tren vi dieu khien */
sizeof(long) == 8          /* la 4 tren Windows 64 bit */
char co dau                /* khong dau tren ARM32 va PowerPC */
CHAR_BIT == 8              /* gan nhu luon dung, nhung khong chuan */
nho truoc, little endian   /* mang va mot so CPU la lon truoc */
NULL la toan bit 0         /* bieu dien khong duoc bao dam */
con tro va so nguyen cung do rong   /* khong bao dam */
Cách viết an toàn
#include <stdint.h>
#include <limits.h>

int32_t   x;               /* dung n bit o moi may */
size_t    n;               /* cho kich thuoc va chi so */
uintptr_t p_so;            /* du chua con tro */
unsigned char b;           /* cho du lieu byte */

_Static_assert(CHAR_BIT == 8, "can byte 8 bit");

/* Doc va ghi so nhi phan bang dich bit, Bai 33.7: */
uint32_t doc_u32(const unsigned char *t) {
    return (uint32_t)t[0] << 24 | (uint32_t)t[1] << 16
         | (uint32_t)t[2] <<  8 | (uint32_t)t[3];
}

/* Va gan NULL bang cach viet NULL, khong bang memset ve 0: */
p = NULL;                  /* dung */
memset(&p, 0, sizeof p);   /* KHONG bao dam cho ra con tro NULL */
terminal
# Đo trên MinGW-W64, x86-64, Windows
./impl
sizeof(int)       = 4
sizeof(long)      = 4
sizeof(long long) = 8
sizeof(void *)    = 8
CHAR_BIT          = 8
CHAR_MIN          = -128  -> char CO dau

#Kiểm chứng khả chuyển

Sáu cấu hình đáng dịch thử
# 1. Chuan nghiem ngat
gcc -std=c17 -pedantic-errors -Wall -Wextra prog.c

# 2. 32 bit, bat gia dinh ve kich thuoc con tro
gcc -m32 -std=c17 prog.c

# 3. Trinh bien dich khac, bat canh bao khac
clang -std=c17 -Wall -Wextra prog.c
clang -Weverything prog.c          # BAT MOI canh bao, rat nhieu bao gia

# 4. Nen tang khac
x86_64-w64-mingw32-gcc prog.c      # Windows tu Linux
arm-none-eabi-gcc prog.c           # ARM, char khong dau

# 5. char khong dau, gia lap ARM tren x86
gcc -funsigned-char prog.c

# 6. Sanitizer, bat gia dinh sai luc chay
gcc -fsanitize=undefined,address prog.c
Ma trận kiểm tra trong tích hợp liên tục
# .github/workflows/kiem.yml, dai y

chien_luoc:
  ma_tran:
    trinh_bien_dich: [gcc, clang]
    chuan:           [c99, c11, c17]
    do_rong:         [32, 64]
    he:              [ubuntu, macos, windows]

buoc:
  - chay: |
      $CC -std=$CHUAN -m$DO_RONG \
          -pedantic-errors -Wall -Wextra -Werror \
          -fsanitize=undefined,address \
          -o thu *.c
      ./thu

# 2 x 3 x 2 x 3 = 36 to hop. Nhieu, nhung moi to hop chay vai giay
# va chung chay song song.
#
# Trong thuc te, bon to hop dau tien bat gan het van de:
#   gcc  c17 64 ubuntu
#   clang c17 64 macos
#   gcc  c99 32 ubuntu
#   mingw c17 64 windows

Tự làm thử

  1. Tải N1570 và tìm mục nói về số học con trỏ.
  2. Đọc hết phụ lục J.2 và đánh dấu năm mục bạn từng vi phạm.
  3. Tra trong chuẩn xem fopen trả về gì khi thất bại.
  4. Dịch một dự án với -funsigned-char và đếm số lỗi mới.
  5. Chạy clang -Weverything và chọn ra năm cảnh báo đáng bật thường xuyên.
  6. Viết ma trận kiểm tra bốn cấu hình cho một dự án của bạ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

  • Bản nháp N1570 cho C11 miễn phí và khác bản chính thức không đáng kể.
  • Mục 6 là ngôn ngữ, mục 7 là thư viện, và phụ lục J liệt kê toàn bộ hành vi không xác định.
  • shall là bắt buộc, may là được phép, và chú thích ở chân trang không có hiệu lực nhưng rất hữu ích để hiểu.
  • Chuẩn bảo đảm phạm vi tối thiểu chứ không bảo đảm kích thước, và sizeof(char) bằng 1 theo định nghĩa.
  • -funsigned-char là cách rẻ nhất để giả lập ARM trên x86 và bắt lỗi phụ thuộc vào dấu của char.