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

Implementation-defined và unspecified

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

  • Phân biệt implementation-defined với unspecified bằng ví dụ
  • Kể năm thứ implementation-defined hay gây lỗi khi chuyển máy
  • Tra tài liệu trình biên dịch để biết lựa chọn của nó
  • Viết mã không phụ thuộc vào những lựa chọn đó

Đây là nhóm hành vi hiền lành nhất trong ba nhóm: chương trình vẫn có nghĩa, chỉ là kết quả khác nhau giữa các máy. Nhưng chính vì hiền lành mà nó sống sót qua mọi bài kiểm thử, rồi lộ ra đúng lúc bạn chuyển sang nền tảng khác.

#Phân biệt bằng hai câu hỏi

Implementation-definedUnspecified
Trình biên dịch phải ghi vào tài liệuCóKhông
Phải nhất quán trong cùng một chương trìnhCóKhông
Tra cứu trước đượcCóKhông
Chương trình vẫn hợp lệCóCó
Ví dụsizeof(int), char có dấuThứ tự đánh giá đối số

#Kích thước kiểu

terminal
# MinGW-W64, x86-64, Windows 11
gcc -std=c11 -O2 -o impl impl.c && ./impl
sizeof(char)      = 1
sizeof(short)     = 2
sizeof(int)       = 4
sizeof(long)      = 4
sizeof(long long) = 8
sizeof(void *)    = 8
CHAR_BIT          = 8
KiểuWindows 64 bitLinux 64 bitVi điều khiển 16 bitChuẩn bảo đảm
char111Đúng 1, theo định nghĩa
short222Ít nhất 2
int442Ít nhất 2
long484Ít nhất 4
long long888Ít nhất 8
void *882 hoặc 4Không bảo đảm gì
size_t882 hoặc 4Đủ chứa sizeof lớn nhất

#char có dấu hay không

terminal
./impl
CHAR_MIN          = -128  -> char CO dau
char c = 200      -> -56
Dùng char trần cho dữ liệu byte
char c = doc_byte();

if (c == 0xFF) { }              /* KHONG BAO GIO dung khi char co dau:
                                   0xFF la 255, con c toi da la 127 */

if (c > 127) { }                /* luon sai khi char co dau */

int n = c;                      /* 0xFF thanh -1 hay 255? Tuy may */

int chi_so = ten[i];            /* neu ten[i] am thi truy cap ngoai bien */
bang[chi_so]++;
unsigned char cho dữ liệu
unsigned char c = doc_byte();

if (c == 0xFF) { }              /* dung */

int n = c;                      /* luon 0..255 */

int chi_so = (unsigned char)ten[i];   /* luon khong am */
bang[chi_so]++;

/* Quy tac:
     char           -> KY TU van ban
     unsigned char  -> BYTE du lieu tho
     signed char    -> so nho co dau, hiem khi can */

#Thứ tự byte

Kiểm tra thứ tự byte lúc chạy
#include <stdio.h>
#include <stdint.h>
#include <string.h>

int main(void) {
    uint32_t x = 0x01020304;
    unsigned char b[4];

    memcpy(b, &x, sizeof b);

    printf("0x01020304 trong bo nho: %02X %02X %02X %02X\n",
           b[0], b[1], b[2], b[3]);

    printf("May nay: %s\n", b[0] == 0x04 ? "nho truoc" : "lon truoc");

    return 0;
}
terminal
gcc -std=c11 -O2 -o bo bo.c && ./bo
0x01020304 trong bo nho: 04 03 02 01
May nay: nho truoc

#Bốn thứ còn lại

Dịch phải số âm

terminal
./impl
-8 >> 1           = -4  (dich so hoc)
int am = -8;
am >> 1;                  /* -4 tren may nay: dich SO HOC, giu dau.

   Chuan noi day la implementation-defined: cai dat duoc chon
   dich so hoc hoac dich logic.

   Thuc te moi trinh bien dich hien dai deu dich so hoc, va C23
   da chuan hoa luon. Nhung neu ban viet ma cho C11 tro xuong
   thi van nen tranh. */

/* Cach chac chan: chuyen sang khong dau truoc. */
unsigned u = (unsigned)am;
u >> 1;                   /* luon dich logic */

Chia số âm

terminal
./impl
-7 / 2            = -3
-7 % 2            = -1
-7 / 2      /* C99 tro len: -3, cat VE PHIA KHONG.
               Truoc C99: implementation-defined, co the -4 */

-7 % 2      /* -1. Dau cua phan du theo dau cua SO BI CHIA */

/* Quy tac C99: (a / b) * b + a % b == a luon dung.

   Chu y day KHAC voi Python, noi -7 // 2 la -4 va -7 % 2 la 1.
   Neu ban chuyen thuat toan tu Python sang C thi phai kiem lai. */

/* Muon chia lam tron xuong nhu Python: */
int chia_xuong(int a, int b) {
    int q = a / b;
    if ((a % b != 0) && ((a < 0) != (b < 0))) --q;
    return q;
}

Trường bit

struct {
    unsigned a : 3;
    unsigned b : 5;
} s;

/* Implementation-defined:
     - a nam o bit thap hay bit cao cua byte dau
     - co byte dem giua a va b khong
     - "unsigned x : 3" hop le, nhung "int x : 3" co dau hay khong

   Nghia la KHONG duoc dung truong bit de anh xa dinh dang tep
   hay thanh ghi phan cung. Dung mat na va dich bit, Bai 31.4. */

Kiểu cơ sở của enum

enum Mau { DO, XANH, VANG };

sizeof(enum Mau)          /* implementation-defined: thuong la 4,
                             nhung trinh bien dich duoc chon kieu
                             nho nhat du chua */

/* Nen khong ghi enum thang ra tep, va khong dua enum qua
   ranh gioi ABI ma khong co dinh kieu.

   C23 cho phep chi dinh: */
enum Mau : uint8_t { DO, XANH, VANG };

#Viết mã không phụ thuộc

Kiểm tra giả định ngay lúc dịch
#include <assert.h>
#include <limits.h>
#include <stdint.h>

_Static_assert(CHAR_BIT == 8,
               "ma nay gia dinh byte 8 bit");

_Static_assert(sizeof(int) >= 4,
               "ma nay gia dinh int it nhat 32 bit");

_Static_assert(sizeof(void *) == sizeof(uintptr_t),
               "uintptr_t phai du chua con tro");

_Static_assert(sizeof(SinhVien) == 112,
               "kich thuoc ban ghi doi, phai tang phien ban dinh dang tep");

/* Neu mot gia dinh sai thi chuong trinh KHONG DICH DUOC, kem thong bao
   ban tu viet. Tot hon nhieu so voi viec no dich duoc roi chay sai
   tren may khach.

   Bai 19.7 da gioi thieu _Static_assert. */
terminal
# Cố tình cho sai một giả định để xem thông báo
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,
 ^~~~~~~~~~~~~~

Thông báo là câu bạn tự viết, chứ không phải một thông báo chung chung của trình biên dịch. Người gặp lỗi đọc là hiểu ngay phải làm gì.

Tự làm thử

  1. Chạy chương trình in sizeof mọi kiểu trên hai hệ điều hành khác nhau.
  2. Viết chương trình xác định thứ tự byte của máy bạn.
  3. Chứng minh char c = 200 in ra -56 và giải thích vì sao.
  4. Viết cặp hàm ghi_u32 và doc_u32 rồi kiểm tra chúng đảo ngược nhau.
  5. Thêm bốn dòng _Static_assert vào một dự án của bạn và thử phá vỡ một giả định.
  6. Dịch một dự án cũ với -m32 và đếm số lỗi mới.

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

  • Implementation-defined thì tra cứu được và nhất quán, unspecified thì không. Cả hai đều không phải UB.
  • sizeof(long) là 4 trên Windows và 8 trên Linux, nên ép con trỏ vào long là mã hỏng.
  • char là kiểu thứ ba, khác cả signed char lẫn unsigned char, và dấu của nó tùy nền tảng.
  • Mọi hàm ctype.h phải nhận đối số đã ép sang unsigned char, nếu không là UB.
  • _Static_assert biến một giả định ngầm thành lỗi biên dịch có thông báo do bạn viết.