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

Biểu diễn nhị phân

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

  • Viết hằng số dạng nhị phân, thập lục phân và bát phân
  • Nhận ra cái bẫy số 0 ở đầu biến hằng thành bát phân
  • Viết hàm in bit của một vùng nhớ bất kỳ
  • Đọc biểu diễn bit của số nguyên, số thực và con trỏ

Trước khi thao tác bit, phải nhìn thấy chúng. Bài này viết một hàm in biểu diễn nhị phân của bất kỳ đối tượng nào, và dùng nó để nhìn vào số nguyên, số thực, ký tự và con trỏ.

#Bốn cách viết hằng số

int a = 42;         /* thap phan */
int b = 0x2A;       /* thap luc phan, moi chu so bang 4 bit */
int c = 052;        /* BAT PHAN, vi bat dau bang so 0 */
int d = 0b101010;   /* nhi phan, C23. GCC ho tro tu lau nhu mo rong */

/* Ca bon deu bang 42. */
DạngTiền tốCơ sốChuẩn từDùng khi
42không có10C89Số lượng, chỉ số, giá trị thường
0x2A0x hoặc 0X16C89Mặt nạ bit, địa chỉ, mã byte
05208C89Quyền tệp trên Unix, hầu như không dùng gì khác
0b1010100b hoặc 0B2C23Mẫu bit ngắn, khi muốn nhìn thấy từng bit

#Cái bẫy số 0 ở đầu

Số 0 ở đầu để căn lề
int thang = 08;         /* LOI BIEN DICH:
                           error: invalid digit "8" in octal constant */

int ngay  = 07;         /* Bien dich duoc, nhung bang 7 tinh co */

int gio   = 010;        /* Bien dich duoc, va bang TAM, khong phai muoi */

int ma[] = { 001, 002, 010, 011, 012 };
/* Nguoi viet muon: 1, 2, 10, 11, 12
   Thuc te nhan:    1, 2,  8,  9, 10 */
Không bao giờ viết số 0 ở đầu
int thang = 8;
int ngay  = 7;
int gio   = 10;

int ma[] = { 1, 2, 10, 11, 12 };

/* Muon can le thi dung khoang trang, khong dung so 0: */
int ma2[] = {  1,  2, 10, 11, 12 };
terminal
./bit-demo
42 = 0x2A = 052
052 (bat phan) = 42
0x2A = 42

#Hàm in bit

in-bit.c
#include <stdio.h>
#include <stddef.h>

/* In bieu dien nhi phan cua BAT KY doi tuong nao.
   In tu byte co dia chi CAO nhat xuong thap nhat, nen tren may
   nho truoc thi ket qua doc nhu mot so viet binh thuong. */
void in_bit(const void *obj, size_t n) {
    const unsigned char *p = obj;

    for (size_t i = n; i-- > 0; ) {
        for (int b = 7; b >= 0; --b)
            putchar((p[i] >> b & 1) ? '1' : '0');

        putchar(' ');
    }

    putchar('\n');
}

int main(void) {
    int    x = 42;    printf("int 42     : "); in_bit(&x, sizeof x);
    int    y = -42;   printf("int -42    : "); in_bit(&y, sizeof y);
    float  f = 1.5f;  printf("float 1.5  : "); in_bit(&f, sizeof f);
    double d = 1.5;   printf("double 1.5 : "); in_bit(&d, sizeof d);
    char   c = 'A';   printf("char 'A'   : "); in_bit(&c, sizeof c);
    void  *p = &x;    printf("con tro    : "); in_bit(&p, sizeof p);

    return 0;
}
terminal
gcc -std=c11 -Wall -Wextra -o in-bit in-bit.c && ./in-bit
int 42     : 00000000 00000000 00000000 00101010 
int -42    : 11111111 11111111 11111111 11010110 
float 1.5  : 00111111 11000000 00000000 00000000 
double 1.5 : 00111111 11111000 00000000 00000000 00000000 00000000 00000000 00000000 
char 'A'   : 01000001 
con tro    : 00000000 00000000 00000000 00000000 00000000 01100001 11111110 00111100 

#Đọc kết quả

Số nguyên âm: bù hai

int 42  : 00000000 00000000 00000000 00101010
int -42 : 11111111 11111111 11111111 11010110

/* Doi tu 42 sang -42:
     1. dao het bit:  11111111 11111111 11111111 11010101
     2. cong 1:       11111111 11111111 11111111 11010110

   Do la bu hai. Kiem lai:
     42 =            00000000 ... 00101010
     ~42 =           11111111 ... 11010101
     ~42 + 1 =       11111111 ... 11010110   dung */

/* Tu C23, bu hai la bat buoc. Truoc do chuan cho phep ca
   bu mot va dau va do lon, nhung khong may nao con dung. */

Số thực: dấu, số mũ, định trị

float 1.5 : 0 01111111 10000000000000000000000
            ^ ^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
            |    |             dinh tri, 23 bit
            |    so mu, 8 bit
            dau, 1 bit

/* Giai ma:
     dau     = 0        -> so duong
     so mu   = 01111111 = 127, tru do lech 127 -> 0
     dinh tri= 1000...  -> 1.1 nhi phan = 1.5 thap phan

   Gia tri = 1.5 x 2^0 = 1.5   dung

double 1.5 : 0 01111111111 1000000000...
             ^ ^^^^^^^^^^^ ^^^^^^^^^^^^
             |      |          dinh tri, 52 bit
             |      so mu, 11 bit, do lech 1023
             dau

   Bai 3.4 da noi ve IEEE 754. Bay gio ban nhin thay no. */

Con trỏ

con tro : 00000000 00000000 00000000 00000000 00000000 01100001 11111110 00111100

/* Tam byte, nhung muoi sau bit cao deu bang 0. Tren x86-64,
   khong gian dia chi hien dung 48 bit, phan con lai phai la
   ban sao cua bit 47. Do la ly do dia chi tren Linux va Windows
   luon co dang 0x00007fff... hoac 0xffff8000...

   Vai he thong loi dung 16 bit thua do de nhet co vao con tro,
   goi la con tro co the. Ky thuat do rat nhanh va rat nguy hiem,
   va no hong khi Intel bat che do 5 tang bang trang. */

Tự làm thử

  1. Viết in_bit và chạy nó với int, float, double và một con trỏ.
  2. In 42 và -42 rồi kiểm tra quan hệ bù hai bằng tay.
  3. In 0.1, 0.5 và 0.25 dạng double, chỉ ra cái nào biểu diễn chính xác được.
  4. Viết int a = 010; rồi in a, giải thích kết quả.
  5. Viết chương trình kiểm tra thứ tự byte của máy bạn.
  6. Sửa in_bit để nó in theo thứ tự byte thật trong bộ nhớ, và so hai bản với cùng một số nguyê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 dạng hằng số: thập phân, thập lục phân với 0x, bát phân với số 0 ở đầu, và nhị phân với 0b từ C23.
  • Số 0 ở đầu biến hằng số thành bát phân, và trình biên dịch chỉ báo lỗi khi có chữ số 8 hoặc 9.
  • Mỗi chữ số thập lục phân là đúng bốn bit, nên hex là dạng tự nhiên để viết mặt nạ.
  • Hàm in bit chạy qua const unsigned char *, kiểu duy nhất chuẩn cho phép nhìn mọi đối tượng như dãy byte.
  • Nhìn bit của 0.1 là thấy ngay vì sao số thực không biểu diễn chính xác được.