資訊表示與程式翻譯 (Information & Program Translation)

Overview Table

小節 主題 一句話重點
1.1 (p.39) Information Is Bits + Context 系統中一切資訊都是 bits;同一串 bytes 的意義完全取決於情境 (context)
1.1 Aside (p.40) C 語言起源 C 由 Dennis Ritchie(Bell Labs, 1969–1973)開發,與 Unix 共生、小而簡單、為實用而生
1.2 (p.40-42) 程式的翻譯 hello.cpreprocessor → compiler → assembler → linker 四階段變成可執行檔
1.2 Aside (p.42) GNU 專案 gcc 來自 Stallman 1984 年創立的 GNU 專案;Linux 核心 + GNU 工具 = 完整系統
1.3 (p.42-43) 為何要懂編譯系統 三大理由:最佳化效能理解 link-time 錯誤避免安全漏洞

1.1 資訊 = 位元 + 情境 (Information Is Bits + Context) (p.39)

hello 程式的生命始於程式設計師用編輯器建立的原始程式 (source program / source file) hello.c。原始程式本質上是一串 bit(值為 0 或 1),以 8 個 bit 為一組組織成 byte;每個 byte 代表程式中的某個文字字元。

全書的第一個基本觀念 (fundamental idea)

系統中所有資訊——磁碟檔案、記憶體中的程式與使用者資料、網路上傳輸的資料——都只是一堆 bits。唯一能區分不同資料物件的是我們檢視它們的情境 (context):同一串 bytes 在不同情境下,可能代表整數、浮點數、字串或機器指令。

例外:ASCII 不是唯一編碼

非英語文字使用其他編碼方法(如 Unicode/UTF-8,書中 p.86 的 aside 討論)。「每字元一 byte」的敘述僅對 ASCII 成立。

Aside:C 語言的起源 (p.40)

C 由 Dennis Ritchie(Bell Laboratories)於 1969–1973 年開發;ANSI 於 1989 年批准 ANSI C 標準,後由 ISO 接手。標準定義了 C 語言與 C standard library。K&R(Kernighan & Ritchie)的經典書描述 ANSI C。Ritchie 自評:C 是 "quirky, flawed, and an enormous success"。成功原因:

成功原因 說明
Unix 緊密相連 C 一開始就是 Unix 的系統程式語言;Unix kernel 與其工具/函式庫幾乎全以 C 寫成,Unix 因此易於移植,又擴大了 C 的使用者群
小而簡單 設計由單一人主導而非委員會,乾淨一致;K&R 一書僅 261 頁即涵蓋完整語言與標準函式庫
為實用目的而設計 C 為實作 Unix 而生;後來大家發現寫想寫的程式時語言不會礙事

1.2 程式被其他程式翻譯成不同形式 (p.40-42)

hello 以高階 C 程式的形式誕生(人類可讀),但要在系統上執行,必須由其他程式把每條 C 敘述翻譯成一連串低階機器語言指令 (machine-language instructions),再打包成可執行目的程式 (executable object program),以二進位磁碟檔的形式儲存(也稱 executable object files)。

在 Unix 系統上,由編譯器驅動程式 (compiler driver) 完成整個翻譯:

linux> gcc -o hello hello.c

執行四階段翻譯的程式(preprocessor、compiler、assembler、linker)合稱編譯系統 (compilation system):

                                                                           printf.o
                                                                               |
                                                                               v
 hello.c --> [cpp] --> hello.i --> [cc1] --> hello.s --> [as] --> hello.o --> [ld] --> hello
           Pre-                  Compiler              Assembler            Linker
           processor
 Source      |       Modified      |        Assembly     |     Relocatable    |     Executable
 program     |       source        |        program      |     object         |     object
 (text)      |       program       |        (text)       |     programs       |     program
             |       (text)        |                     |     (binary)       |     (binary)

(Figure 1.3, p.41)

四階段比較表

階段 程式 輸入 → 輸出 檔案型態 做什麼
Preprocessing cpp hello.chello.i text → text # 開頭的 directive 修改原始 C 程式;如 #include <stdio.h> 會把系統標頭檔 stdio.h 的內容直接插入程式文字
Compilation cc1 hello.ihello.s text → text 翻譯成組合語言程式 (assembly-language program)
Assembly as hello.shello.o text → binary 翻譯成機器語言指令並打包為 relocatable object program;用文字編輯器打開會是亂碼
Linking ld hello.o + printf.ohello binary → binary 合併預編譯目的檔;產出可載入記憶體執行的 executable object file

各階段細節(p.41-42):

main:
  subq    $8, %rsp
  movl    $.LC0, %edi
  call    puts
  movl    $0, %eax
  addq    $8, %rsp
  ret

每一行對應一條低階機器指令的文字形式(深入見 03-Machine-Level-Programs/01-Program-Encodings-and-Data-Formats)。

記憶口訣:副檔名鏈

.c(source, text)→ .i(modified source, text)→ .s(assembly, text)→ .o(relocatable object, binary)→ 無副檔名 executable(binary)。前三個是文字檔、後兩個是二進位檔;text/binary 的分界在 assembler。

常見混淆

  • gcccompiler driver(驅動四個階段),不是狹義的 compiler;狹義 compiler 是 cc1
  • hello.orelocatable object file,不能直接執行;要經過 linking 才成為 executable。
  • 書中組語範例呼叫的是 puts 而非 printf——這是編譯器對「無格式化參數的 printf」做的最佳化,屬於一般化敘述的例外。

Aside:GNU 專案 (p.42)


1.3 理解編譯系統如何運作是值得的 (p.42-43)

hello.c 這種簡單程式,可以放心信任編譯系統產出正確且有效率的機器碼;但程式設計師仍需理解編譯系統運作的三大理由:

理由 核心問題(書中列舉) 對應章節
最佳化程式效能 (Optimizing program performance) switch 是否總比一連串 if-else 有效率?函式呼叫的 overhead 多大?while 是否比 for 快?pointer 存取是否比 array index 快?為何加總到區域變數比加總到 pass-by-reference 參數快得多?為何重排算式括號能讓函式變快? Ch.3 (x86-64 機器語言)、Ch.5(調校 C 程式)、Ch.6(記憶體階層與陣列儲存)
理解 link-time 錯誤 (Understanding link-time errors) linker 回報 unresolved reference 是什麼意思?static 變數與 global 變數的差別?兩個 C 檔定義同名 global 變數會怎樣?static library 與 dynamic library 的差別?為何命令列上函式庫的順序很重要?最可怕的:為何某些 linker 相關錯誤直到 run time 才出現? Ch.7(見 07-Linking/02-Symbol-Resolution-and-Relocation)
避免安全漏洞 (Avoiding security holes) buffer overflow 漏洞多年來是網路/Internet 伺服器安全漏洞的主因,根源是程式設計師不了解必須嚴格限制從不可信來源接收資料的數量與形式;安全程式設計的第一步是理解資料與控制資訊在 program stack 上的儲存方式與後果 Ch.3(stack discipline 與 buffer overflow,見 03-Machine-Level-Programs/06-Buffer-Overflow-and-Pointer-Safety);並學習程式設計師/編譯器/OS 降低攻擊威脅的方法

Exam/Test Patterns

情境 / 關鍵字 答案
「同一串 bytes 可能是整數、浮點數、字串或指令」考什麼觀念? Information is bits + context——區分資料物件的唯一因素是檢視的情境
hello.c 中字元 # 的 ASCII 值? 35;'\n'10,空白 SP 是 32
只含 ASCII 字元的檔案叫什麼?其他呢? text file;其他一律是 binary file
機器裡的數與數學的整數/實數關係? 有限近似 (finite approximations),可能有出乎意料的行為(Ch.2)
編譯系統四階段依序為? Preprocessing (cpp) → Compilation (cc1) → Assembly (as) → Linking (ld)
hello.i / hello.s / hello.o 各是什麼? modified source(text)/ assembly program(text)/ relocatable object program(binary)
#include <stdio.h> 由哪個階段處理?做什麼? Preprocessor (cpp):把 stdio.h 內容直接插入程式文字,輸出 .i
為何組合語言「有用」? 為不同高階語言(C、Fortran…)的編譯器提供共同輸出語言
printf 的程式碼從哪來?誰合併? 位於 standard C library 的預編譯目的檔 printf.o;由 linker (ld)hello.o 合併
.o 檔可以直接執行嗎? 不行,relocatable object file 必須先 link 成 executable
哪個工具是 compiler driver? gcc(驅動 cpp、cc1、as、ld 全部四階段)
GNU 專案缺了哪個 Unix 元件? kernel(由 Linux 專案開發);GNU 提供 emacs、gcc、gdb、assembler、linker 等
需要懂編譯系統的三大理由? 最佳化效能、理解 link-time 錯誤、避免安全漏洞(buffer overflow)
buffer overflow 漏洞的根本原因? 未嚴格限制從不可信來源接受資料的數量與形式;須理解 stack 上資料與控制資訊的儲存方式