資訊儲存與位元運算 (Information Storage & Bit Operations)
Overview Table
| 小節 | 主題 | 核心結論 |
|---|---|---|
| 2.1 | 資訊儲存 (p.70) | 記憶體最小可定址單位是 byte(8 bits),程式看到的是 virtual memory 位元組陣列 |
| 2.1.1 | 十六進位表示法 (p.72) | 1 hex digit = 4 bits;C 語言以 0x 前綴表示 |
| 2.1.2 | 資料大小 (p.75) | word size w 決定 virtual address space 上限 |
| 2.1.3 | 定址與位元組順序 (p.78) | little endian(低位在前)vs big endian(高位在前) |
| 2.1.4 | 字串表示 (p.85) | ASCII + null 結尾;文字資料比二進位資料跨平台 |
| 2.1.5 | 程式碼表示 (p.85) | 機器碼是位元組序列,不可跨平台移植 |
| 2.1.6 | 布林代數 (p.86) | ~ & | ^ 對 bit vector 逐位運算;可編碼有限集合 |
| 2.1.7 | C 位元運算 (p.90) | 適用所有整數型別;常用於 masking |
| 2.1.8 | C 邏輯運算 (p.92) | && || ! 只回傳 0/1,且有 short-circuit 短路求值 |
| 2.1.9 | C 移位運算 (p.93) | 左移補 0;右移分 logical(補 0)與 arithmetic(補符號位) |
2.1 資訊儲存:記憶體即位元組陣列 (p.70)
多數電腦不以個別 bit 為單位存取記憶體,而以 8-bit 的 byte 作為最小可定址單位 (smallest addressable unit)。機器層級程式將記憶體視為一個巨大的位元組陣列,稱為 virtual memory;每個 byte 由唯一編號 address 識別,所有可能位址的集合構成 virtual address space。
- Virtual address space 只是呈現給程式的概念性影像;實際由 DRAM、flash、磁碟、特殊硬體與 OS 軟體共同實作(詳見 09-Virtual-Memory/01-Address-Spaces-and-VM-Caching 對應章節)。
- C 的 pointer 值 = 某儲存區塊第一個 byte 的 virtual address;compiler 為 pointer 維護型別資訊以產生不同機器碼。
- 但編譯後的機器碼沒有任何型別資訊——程式物件只是一塊 bytes,程式本身也只是一串 bytes。
Virtual Memory(概念圖:巨大位元組陣列)
address: 0x000 0x001 0x002 0x003 ... 0x100 0x101 ... 2^w - 1
+------+------+------+------+ +------+------+
| byte | byte | byte | byte | ... | byte | byte | ...
+------+------+------+------+ +------+------+
程式資料 / 指令 / 控制資訊 全部存於此陣列中
Bell Labs C → ANSI C (1989) → ISO C90 → ISO C99(引入固定大小型別如 int32_t)→ ISO C11。gcc 以 -std=c99、-std=c11、-ansi(= -std=c89)等選項切換標準;不加選項時預設為 GNU 89 混合版。
2.1.1 Hexadecimal Notation (p.72)
一個 byte 為 8 bits,二進位範圍 0-9 + A-F)描述 bit pattern。C 中以 0x 或 0X 開頭,大小寫皆可(0xFA1D37B = 0xfa1d37b)。
| Hex | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Dec | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
| Bin | 0000 | 0001 | 0010 | 0011 | 0100 | 0101 | 0110 | 0111 | 1000 | 1001 | 1010 | 1011 | 1100 | 1101 | 1110 | 1111 |
轉換方法(常考手算):
- hex → binary:每個 hex digit 直接展開成 4 bits。
- binary → hex:從右邊開始每 4 bits 一組;若總位數非 4 的倍數,最左組位數不足(等效補前導 0)。
- 2 的冪 → hex:
,令 ( ),則 hex 表示為前導數字 (即 1、2、4、8)後接 個 0。例: , → 0x800。 - decimal → hex:反覆除以 16,
,餘數 由低位往高位排。例: 0x4CB2C。 - hex → decimal:各 digit 乘上 16 的冪加總。例:
0x7AF。
只需背 A=10、C=12、F=15,其餘 B、D、E 可由相鄰值推得(B=A+1、D=C+1、E=F-1)。
2.1.2 Data Sizes (p.75)
每台電腦有一個 word size,表示 pointer 資料的標稱大小。因 virtual address 以一個 word 編碼,word size 決定最重要的系統參數——virtual address space 的最大值:w-bit 機器的位址範圍為
- 32-bit word size → 上限 4 GB(約
bytes);64-bit → 16 EB(約 bytes)。 gcc -m32編譯出的程式可在 32/64-bit 機器執行;gcc -m64只能在 64-bit 機器執行——「32-bit 程式 / 64-bit 程式」是編譯方式的區別,不是機器的區別。- ISO C99 引入固定大小型別
int32_t/int64_t(恰好 4 / 8 bytes),是精確控制資料表示的最佳方式。 - C 標準只規定各型別數值範圍的下限(除固定大小型別外無上限);可攜式程式不應假設確切大小。
| C 宣告 (signed) | unsigned 版本 | 32-bit 程式 | 64-bit 程式 |
|---|---|---|---|
[signed] char |
unsigned char |
1 | 1 |
short |
unsigned short |
2 | 2 |
int |
unsigned |
4 | 4 |
long |
unsigned long |
4 | 8 |
int32_t |
uint32_t |
4 | 4 |
int64_t |
uint64_t |
8 | 8 |
char * |
— | 4 | 8 |
float |
— | 4 | 4 |
double |
— | 8 | 8 |
char 的正負號未定 (p.77)
C 標準不保證 char 是 signed(雖多數編譯器如此)。需要 1-byte signed 值時必須明寫 signed char。另外 unsigned long、unsigned long int、long unsigned、long unsigned int 為同義宣告。
許多舊程式假設 int 可存放 pointer——32-bit 程式沒問題,但 64-bit 程式中 pointer 為 8 bytes、int 只有 4 bytes,遷移時產生隱藏 bug。跨型別大小的可攜寫法是使用 sizeof(T) 與固定大小型別。
2.1.3 Addressing and Byte Ordering (p.78)
跨多 byte 的物件需要兩個慣例:(1) 物件的位址 = 所占 bytes 中最小的位址,且多 byte 物件幾乎都存為連續位元組;(2) bytes 在記憶體中的排列順序。設 w-bit 整數 bit 表示為
- Little endian:least significant byte 放在最低位址(最前)。Intel 相容機器、Android/iOS 上的 ARM 皆是。
- Big endian:most significant byte 放在最前。多數 IBM、Oracle(Sun SPARC)機器。
- Bi-endian 硬體(如 ARM)兩者皆可,但實務上 OS 選定後即固定;兩種順序沒有技術優劣,純屬慣例(名稱出自 Gulliver's Travels)。
int x = 0x01234567,位址 &x = 0x100(int 為 4 bytes)
Big endian: 0x100 0x101 0x102 0x103
... | 01 | | 23 | | 45 | | 67 | ... 高位 byte 在前
Little endian: 0x100 0x101 0x102 0x103
... | 67 | | 45 | | 23 | | 01 | ... 低位 byte 在前
位元組順序通常對程式設計師不可見,但三種情境會浮現 (p.79-80):
- 網路傳輸:little-endian 與 big-endian 機器互傳二進位資料會反序,故網路程式必須依循 network standard 進行 byte order 轉換(見 11-Network-Programming/01-Client-Server-Model-and-Networks 對應章節)。
- 閱讀 disassembler 輸出:如 x86-64 指令 bytes
01 05 43 0b 20 00中,後 4 bytes43 0b 20 00反序讀為0x200b43——little-endian 機器上機器碼中的數值需反序解讀。 - 繞過型別系統:用 cast 或 union 以不同型別檢視同一物件(系統程式設計常用,一般應用不建議)。
檢視位元組表示的工具程式 (p.80-82):typedef unsigned char *byte_pointer;,show_bytes(byte_pointer start, size_t len) 以 printf(" %.2x", start[i]) 逐 byte 印出十六進位;size_t 是表達資料結構大小的首選型別。show_int / show_float / show_pointer 均以 (byte_pointer) &x cast 後傳入,長度用 sizeof(T) 取得——cast 不改變 pointer 的值,只改變編譯器解讀資料的方式;sizeof(T) 回傳型別 T 所需 bytes 數,是可攜性的關鍵。
實測 12345(hex 0x00003039)的結果 (p.82):
| 機器 | int 12345 |
float 12345.0 |
int * &ival |
|---|---|---|---|
| Linux 32 (IA32) | 39 30 00 00 |
00 e4 40 46 |
4 bytes,值機器相依 |
| Windows (IA32) | 39 30 00 00 |
00 e4 40 46 |
4 bytes,值機器相依 |
| Sun (SPARC) | 00 00 30 39 |
46 40 e4 00 |
4 bytes,值機器相依 |
| Linux 64 (x86-64) | 39 30 00 00 |
00 e4 40 46 |
8 bytes,值機器相依 |
int與float各機器內容相同、僅 byte order 不同;pointer 值則完全機器相依(儲存配置慣例不同)。- 同值 12345 的
int(0x00003039)與float(0x4640E400)編碼完全不同,但展開成二進位並適當位移後有 13 個連續相符 bits——伏筆見 02-Information-Representation/04-Floating-Point。
T *p;宣告指向型別 T 的 pointer;&x取址建立 pointer,cast 只改變解讀方式;void *是不帶型別資訊的特殊 pointer。typedef為型別命名,語法同變數宣告(名稱位置放型別名)。- pointer 與 array 可互換使用:
start[i]即讀取start所指位置後第 i 個 byte。 printf格式:%d十進位、%f浮點、%c字元、%.2x至少 2 位十六進位。
2.1.4 Representing Strings (p.85)
C 字串 = 以 null 字元(值 0)結尾的字元陣列,每個字元以標準編碼表示,最常見為 ASCII。
show_bytes("12345", 6)輸出31 32 33 34 35 00:十進位數字字元 x 的 ASCII 碼恰為0x3x,結尾 byte 為0x00。- 任何使用 ASCII 的系統結果都相同,與 byte ordering、word size 無關 → 文字資料比二進位資料更平台無關 (platform independent)。
- 小寫字母
a~z的 ASCII 碼為0x61~0x7A;可用man ascii查表 (p.84)。
ASCII 只夠編碼英文;Unicode 基底編碼(Universal Character Set)用 32-bit 表示字元,而 UTF-8 以變長位元組序列編碼,常見字元僅需 1-2 bytes,且 ASCII 字元在 UTF-8 中保持相同的單 byte 編碼——所有 ASCII 位元組序列在 UTF-8 下意義不變。Java 字串原生使用 Unicode。
2.1.5 Representing Code (p.85)
同一個 C 函式 int sum(int x, int y) { return x + y; } 在不同平台編譯出完全不同的機器碼位元組序列(Linux 32 / Windows / Sun / Linux 64 各異)。
- 不同機器型別使用不同且不相容的指令與編碼;甚至相同處理器 + 不同 OS 的編碼慣例也不同,不具 binary compatibility。
- 二進位程式碼幾乎不可跨「機器 + OS」組合移植。
- 核心觀念:對機器而言,程式就只是一串 bytes;機器不保有原始碼的任何資訊(頂多除錯輔助表)。詳見 03-Machine-Level-Programs/01-Program-Encodings-and-Data-Formats。
2.1.6 Introduction to Boolean Algebra (p.86)
Boolean algebra(George Boole, ~1850)將邏輯值 true/false 編碼為 1/0;Claude Shannon 1937 年碩士論文首次將布林代數連結到數位邏輯(電機繼電器網路)的設計與分析。最簡單的布林代數定義於二元素集合
| 運算 | 邏輯意義 | 真值表重點 |
|---|---|---|
~p(not, ¬) |
反相 | ~0=1、~1=0 |
p & q(and, ∧) |
兩者皆 1 才是 1 | 1&1=1,其餘 0 |
p | q(or, ∨) |
任一為 1 即 1 | 0|0=0,其餘 1 |
p ^ q(xor, ⊕) |
恰一個為 1 才是 1 | 0^1=1^0=1;0^0=1^1=0 |
Bit vector 擴充:對長度 w 的位元向量 |、^、~ 同理逐位擴充。例(w=4):0110 & 1100 = 0100、0110 | 1100 = 1110、0110 ^ 1100 = 1010、~1100 = 0011。
重要代數性質(p.88 Web Aside DATA:BOOL):
&對|分配:;且 |也對&分配:(整數加法/乘法沒有後者的對應性質)。 ^、&、~構成 Boolean ring:每個元素是自己的加法反元素——,且 (這是 XOR swap 技巧的理論基礎)。
Bit vector 編碼有限集合 (p.88):子集 | = 聯集 ∪、& = 交集 ∩、~ = 補集。例:[01101001] 編碼
2.1.7 Bit-Level Operations in C (p.90)
C 直接支援位元運算 | & ~ ^(符號與布林代數一致),可套用於任何整數型別("integral" data type)。求值訣竅:hex 展開成 binary → 逐位運算 → 轉回 hex。
| C 運算式 | 二進位運算 | 結果 |
|---|---|---|
~0x41 |
~[0100 0001] |
[1011 1110] = 0xBE |
~0x00 |
~[0000 0000] |
[1111 1111] = 0xFF |
0x69 & 0x55 |
[0110 1001] & [0101 0101] |
[0100 0001] = 0x41 |
0x69 | 0x55 |
[0110 1001] | [0101 0101] |
[0111 1101] = 0x7D |
Masking(最常見用途,p.91):mask 是標示「選取哪些 bits」的 bit pattern。
x & 0xFF取出 x 的最低位 byte,其餘清 0。例:0x89ABCDEF & 0xFF = 0x000000EF。~0產生全 1 的 mask,且與 word size 無關——比寫死0xFFFFFFFF更可攜。
XOR swap(inplace_swap, p.90;利用 a^a = 0、(a^b)^a = b):
*x *y
init: a b
step1: a a^b (*y = *x ^ *y)
step2: a^(a^b) = b a^b (*x = *x ^ *y)
step3: b (a^b)^b = a (*y = *x ^ *y)
用 inplace_swap 反轉奇數長度陣列時,最後一輪 first == last,x 與 y 指向同一位置:step 1 就把它變成 a ^ a = 0,中間元素被清成 0。修正法:迴圈條件改為 first < last。此技巧亦無效能優勢,純屬趣味。
bis(x, m)(bit set)= x | m;bic(x, m)(bit clear)= x & ~m。由此可組合出 x | y = bis(x, y)、x ^ y = bis(bic(x, y), bic(y, x))(即
2.1.8 Logical Operations in C (p.92)
C 的邏輯運算子 && || ! 對應邏輯上的 or、and、not,與位元運算行為截然不同,極易混淆:
| 面向 | 位元運算 & | ~ |
邏輯運算 && || ! |
|---|---|---|
| 運算對象 | 逐 bit | 整個值:非 0 = true,0 = false |
| 回傳值 | 任意 bit pattern | 只有 0x01(true)或 0x00(false) |
| 求值方式 | 兩邊都求值 | short-circuit:第一個引數已可決定結果時,不求值第二個 |
範例 (p.93):!0x41 = 0x00、!0x00 = 0x01、!!0x41 = 0x01、0x69 && 0x55 = 0x01、0x69 || 0x55 = 0x01。
- 只有當引數限定為 0 或 1 時,位元運算行為才與邏輯運算一致。
- Short-circuit 的實用性:
a && 5/a永不除以 0;p && *p++永不解參考 null pointer。 - 判斷相等的位元技巧 (Practice 2.15):
x == y等價於!(x ^ y)。
2.1.9 Shift Operations in C (p.93)
設 x 的 bit 表示為
- 左移
x << k:得——丟棄最高 k bits,右端補 k 個 0。 - 右移
x >> k有兩種形式:- Logical(邏輯右移):左端補 k 個 0,得
。 - Arithmetic(算術右移):左端補 k 個最高位 bit
,得 ——對 signed 資料有用(保持符號)。
- Logical(邏輯右移):左端補 k 個 0,得
- 移位運算左結合:
x << j << k=(x << j) << k;移位量應介於。
8-bit 範例 (p.94):
x = [01100011] x = [10010101]
x << 4 = [00110000] [01010000] 右端補 0
x >> 4 (logical) = [00000110] [00001001] 左端補 0
x >> 4 (arith.) = [00000110] [11111001] 左端補 MSB
(MSB=0,兩種相同) (MSB=1,結果不同!)
C 標準沒有規定 signed 數該用算術或邏輯右移——依賴其一即有移植性風險。實務上幾乎所有 compiler/machine 組合對 signed 資料用算術右移;對 unsigned 資料則必須用邏輯右移。Java 則明確定義:x >> k 為算術、x >>> k 為邏輯右移。
C 標準未定義 << 32、>> 36、>> 40 實為移 0、4、8 位)——但不保證,移位量務必小於 word size。Java 則明定採
1 << 2 + 3 << 4 會被解析為 (1 << (2+3)) << 4 = 512,而非直覺的 (1<<2) + (3<<4) = 52,因為加減法優先權高於移位。不確定就加括號!
Exam/Test Patterns
| 情境 / 關鍵字 | 答案 |
|---|---|
| binary ↔ hex 轉換 | 每 4 bits 對 1 hex digit;bits 非 4 倍數時最左組補前導 0 |
0x800 |
|
| decimal → hex | 反覆 |
| w-bit word size 的最大 VA 空間 | |
64-bit 程式中 long / pointer 大小 |
各 8 bytes;int 仍為 4 bytes |
int x = 0x01234567 在 little endian 位址 0x100 起的排列 |
67 45 23 01(低位 byte 在低位址) |
| 給 show_bytes 輸出判斷 endianness | 最低有效 byte 先印出 → little endian;最後印出 → big endian |
| disassembly 中讀出 little-endian 常數 | 位元組反序重組(43 0b 20 00 → 0x200b43) |
字串 "12345" 的 bytes |
31 32 33 34 35 00(ASCII 數字 x = 0x3x,null 結尾);與 endianness、word size 無關 |
| 取 x 最低 byte / 全 1 mask | x & 0xFF;~0(可攜,與 word size 無關) |
a ^ a 的值;(a ^ b) ^ a |
0;b(Boolean ring:元素為自身加法反元素) |
| XOR swap 對同一位址呼叫 | 元素被清成 0(奇數長度陣列反轉 bug;改用 first < last) |
!x vs ~x |
!0x41=0x00(邏輯);~0x41=0xBE(位元逐位反相) |
a && 5/a 為何安全 |
&& short-circuit:a 為 0 時不求值 5/a |
x == y 只用位元/邏輯運算 |
!(x ^ y) |
unsigned 的 >> |
必為 logical shift(補 0) |
signed 的 >> |
C 未定義;實務上幾乎都是 arithmetic shift(補 MSB) |
| shift 量 ≥ w | 未定義行為;許多機器算 |
1 << 2 + 3 << 4 的值 |
512(加減法優先權高於移位;移位左結合) |
bit vector 表示集合時 | & ~ |
聯集、交集、補集 |
bis / bic 對應 C 運算 |
bis(x,m) = x | m;bic(x,m) = x & ~m |
Related Notes
- 02-Information-Representation/02-Integer-Representations — unsigned 與 two's-complement 編碼,承接本節的 bit pattern 基礎
- 02-Information-Representation/03-Integer-Arithmetic — 移位實作乘除法、overflow 行為
- 02-Information-Representation/04-Floating-Point — 12345 的 int/float 編碼中 13 個相符 bits 的解答
- 01-Computer-Systems-Tour/01-Information-and-Program-Translation — 「資訊 = bits + context」的總覽視角
- 03-Machine-Level-Programs/01-Program-Encodings-and-Data-Formats — 機器碼位元組表示與 disassembler
- 11-Network-Programming/01-Client-Server-Model-and-Networks — network byte order 與 endianness 轉換的實務場景