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

Sáu toán tử bit và ba cạm bẫy

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

  • Dùng sáu toán tử bit và biết bảng chân trị của từng cái
  • Nêu ba trường hợp dịch bit gây hành vi không xác định
  • Giải thích vì sao thao tác bit phải dùng kiểu không dấu
  • Nhớ rằng toán tử bit có độ ưu tiên thấp hơn phép so sánh

Sáu toán tử, mỗi cái một dòng để học. Rồi ba trường hợp hành vi không xác định và một cái bẫy độ ưu tiên, và ba thứ đó chiếm phần lớn thời gian gỡ lỗi của người mới làm việc với bit.

#Sáu toán tử

Bốn phép toán trên cùng một cặp giá trị. AND giữ bit chung, OR gộp, XOR giữ bit khác nhau.
Toán tửTênVí dụ với a=12, b=10Kết quảBit ra bằng 1 khi
&AND1100 & 1010 = 10008Cả hai bit vào bằng 1
|OR1100 | 1010 = 111014Ít nhất một bit bằng 1
^XOR1100 ^ 1010 = 01106Đúng một bit bằng 1
~NOT~1100 = ...0011-13Bit vào bằng 0
<<Dịch trái1100 << 1 = 1100024Đẩy sang trái, chèn 0 vào phải
>>Dịch phải1100 >> 1 = 1106Đẩy sang phải

#Dịch trái và dịch phải

x << n      /* dich trai n bit, chen 0 vao ben phai */
x >> n      /* dich phai n bit */

/* Voi so KHONG AM va khong tran: */
x << n   ==  x * 2^n
x >> n   ==  x / 2^n
Biểu thứcGiá trịTương đươngGhi chú
12 << 12412 * 2Nhân đôi
12 << 39612 * 8Nhân 8
12 >> 1612 / 2Chia đôi
13 >> 1613 / 2Cắt bỏ phần lẻ
1u << 3121474836482^31Bit cao nhất của uint32
1 << 31hành vi không xác địnhint không chứa được 2^31

#Ba cạm bẫy dịch bit

Một: dịch bằng hoặc quá độ rộng kiểu

int y = 1;
y << 32;         /* HANH VI KHONG XAC DINH voi int 32 bit */

/* Chuan: neu so lan dich am, hoac lon hon hoac BANG so bit
   cua kieu sau khi thang hang, thi hanh vi khong xac dinh. */
terminal
./dich-ub
y << 32 voi int 32 bit : 1   (UB, ket qua tuy CPU)

Hai: dịch trái số âm

int z = -1;
z << 1;          /* HANH VI KHONG XAC DINH truoc C23 */

/* Ly do lich su: chuan dinh nghia dich trai theo phep nhan 2^n
   tren gia tri, va voi so am thi ket qua co the khong bieu dien
   duoc trong kieu do.

   Tu C23, dich trai duoc dinh nghia lai theo MAU BIT, nen
   -1 << 1 la hop le va cho -2.

   Nhung ma cua ban con phai chay tren trinh bien dich cu.
   Quy tac an toan: KHONG dich so co dau. */
terminal
./dich-ub
(-1) << 1               : -2   (UB truoc C23)

Ba: tràn khi dịch trái số có dấu

int x = 1;
x << 31;         /* HANH VI KHONG XAC DINH: 2^31 khong nam trong int */

unsigned u = 1;
u << 31;         /* dung: 2147483648u, kieu khong dau khong tran */

/* Voi kieu KHONG DAU thi khong co tran: chuan dinh nghia moi phep
   toan la mod 2^n. Voi kieu CO DAU thi tran la hanh vi khong xac dinh.
   Bai 3.5 da noi ve khac biet nay, va o day no quay lai. */

#Vì sao phải dùng kiểu không dấu

Kiểu có dấu cho thao tác bit
int  co = 0;
co |= 1 << 31;           /* UB: tran int */
co &= ~(1 << 5);         /* ~(1 << 5) la so am, va >> sau do
                            se dich so hoc */

int x = -8;
x >> 1;                  /* do cai dat quy dinh */
Kiểu không dấu, và hằng số có hậu tố u
uint32_t co = 0;
co |= 1u << 31;          /* dung */
co &= ~(1u << 5);        /* dung */

uint32_t x = 0xFFFFFFF8u;
x >> 1;                  /* luon la dich logic, chen 0 */
Thao tácKiểu có dấuKiểu không dấu
TrànHành vi không xác địnhĐịnh nghĩa rõ, mod 2^n
Dịch phảiDo cài đặt quy địnhLuôn chèn 0
Dịch trái số âmUB trước C23Không có khái niệm số âm
Bit dấuCó, và nó phá mọi thứKhông có
~xCho số âm, khó đọcCho mẫu bit, dễ đọc

#Độ ưu tiên thấp hơn phép so sánh

if (x & 1 == 0)       /* KHONG phai y ban muon */

/* Do uu tien trong C, tu cao xuong thap:
     ...
     <<  >>
     <   <=  >   >=
     ==  !=
     &
     ^
     |
     &&
     ||

   Nen "x & 1 == 0" duoc doc la "x & (1 == 0)" = "x & 0" = 0. */
terminal
gcc -std=c11 -Wall -o uu-tien uu-tien.c && ./uu-tien
v = 3:  v & 1 == 0 -> 0      (v & 1) == 0 -> 0
v = 4:  v & 1 == 0 -> 0      (v & 1) == 0 -> 1
v = 5:  v & 1 == 0 -> 0      (v & 1) == 0 -> 0
v = 6:  v & 1 == 0 -> 0      (v & 1) == 0 -> 1

Tự làm thử

  1. Tính bằng tay 12 & 10, 12 | 10, 12 ^ 10 rồi kiểm bằng chương trình.
  2. In ~12 dạng nhị phân bằng hàm ở Bài 31.1 và giải thích vì sao nó bằng -13.
  3. Chạy 1 << 32 trên máy bạn và so kết quả với bài.
  4. Viết if (x & 1 == 0) và chạy với bốn giá trị, xác nhận nó luôn sai.
  5. Viết uint16_t c = 0xFFFF; uint32_t d = c << 16; rồi chạy dưới UBSan nếu có.
  6. So hợp ngữ của x / 8 với unsigned và với int, đếm số lệnh.

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

  • Sáu toán tử: AND giữ bit chung, OR gộp, XOR giữ bit khác nhau, NOT đảo, và hai phép dịch.
  • Ba cạm bẫy dịch bit đều là hành vi không xác định: dịch quá độ rộng kiểu, dịch trái số âm, và tràn khi dịch số có dấu.
  • 1 << 32 trên x86 cho ra 1 chứ không phải 0, vì CPU che số lần dịch xuống năm bit.
  • Thao tác bit phải dùng kiểu không dấu có kích thước cố định, và hằng số phải có hậu tố u.
  • & và | có độ ưu tiên thấp hơn ==, nên x & 1 == 0 luôn cho 0. Bọc ngoặc mọi biểu thức bit.