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ử
| Toán tử | Tên | Ví dụ với a=12, b=10 | Kết quả | Bit ra bằng 1 khi |
|---|---|---|---|---|
| & | AND | 1100 & 1010 = 1000 | 8 | Cả hai bit vào bằng 1 |
| | | OR | 1100 | 1010 = 1110 | 14 | Ít nhất một bit bằng 1 |
| ^ | XOR | 1100 ^ 1010 = 0110 | 6 | Đúng một bit bằng 1 |
| ~ | NOT | ~1100 = ...0011 | -13 | Bit vào bằng 0 |
| << | Dịch trái | 1100 << 1 = 11000 | 24 | Đẩy sang trái, chèn 0 vào phải |
| >> | Dịch phải | 1100 >> 1 = 110 | 6 | Đẩ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ức | Giá trị | Tương đương | Ghi chú |
|---|---|---|---|
| 12 << 1 | 24 | 12 * 2 | Nhân đôi |
| 12 << 3 | 96 | 12 * 8 | Nhân 8 |
| 12 >> 1 | 6 | 12 / 2 | Chia đôi |
| 13 >> 1 | 6 | 13 / 2 | Cắt bỏ phần lẻ |
| 1u << 31 | 2147483648 | 2^31 | Bit cao nhất của uint32 |
| 1 << 31 | hành vi không xác định | int 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ác | Kiểu có dấu | Kiểu không dấu |
|---|---|---|
| Tràn | Hành vi không xác định | Định nghĩa rõ, mod 2^n |
| Dịch phải | Do cài đặt quy định | Luôn chèn 0 |
| Dịch trái số âm | UB trước C23 | Không có khái niệm số âm |
| Bit dấu | Có, và nó phá mọi thứ | Không có |
| ~x | Cho số âm, khó đọc | Cho 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ử
- Tính bằng tay
12 & 10,12 | 10,12 ^ 10rồi kiểm bằng chương trình. - In
~12dạng nhị phân bằng hàm ở Bài 31.1 và giải thích vì sao nó bằng -13. - Chạy
1 << 32trên máy bạn và so kết quả với bài. - Viết
if (x & 1 == 0)và chạy với bốn giá trị, xác nhận nó luôn sai. - Viết
uint16_t c = 0xFFFF; uint32_t d = c << 16;rồi chạy dưới UBSan nếu có. - So hợp ngữ của
x / 8vớiunsignedvà vớiint, đế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 << 32trê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ênx & 1 == 0luôn cho 0. Bọc ngoặc mọi biểu thức bit.