個別編譯與相關議題


個別編譯 Separate compilation

為何需要? 方便分工, 減少小幅修改程式時重新編譯所需的時間。

如何保証一致性? 各模組的實作者 (implementor) 與使用者 (user) 透過 header 檔間接檢查彼此的認知是否一致。

範例:

  1. lt_scope.c 分成三個檔案, 分別編譯, 再聯結成可執行檔。
  2. complex.c 分成三個檔案 complex.h, complex.c, test.c, 分別編譯, 再聯結成可執行檔。
  3. sitio.c 提供了文字模式遊標控制的 實作部分 (implementation); sitio.h 則提供了這些副程式的 介面部分 (interface)。 其他程式 (例如 dispath.c) 的程式設計師可以只參考 interface 即可呼叫 sitio.c 所提供的副程式。
  4. 與額外的程式庫聯: pi.c 使用 GNU Multipile Precision (gmp) 程式庫計算 pi 的近似值。

與額外的程式庫聯結, 例: 練習使用 GNU readline 界面, 並以 rlt.c 範例為骨架, 與你寫的 complex.c 聯結, 寫一個簡單的複數計算機。

作業: 使用 date.c 模組, 寫一個類似 UNIX 指令 cal 的程式。

變數的 life time 與 scope

請先閱讀 「程式語言」 講義的 變數篇 裡面相關兩節。 總結來說, 變數按照其知名度的高低可分為:

  1. 全域 (global scope; external linkage): 定義在所有副程式之外。 在所有的程式檔案中都看得到它。 使用前要用 extern 宣告。
  2. 檔案 (file scope; internal linkage): 定義在所有的副程式之外, 加上 static 關鍵字。 在同一個檔案中的所有 (在它之後定義的) 函數都看得到它。
  3. 區段 (block scope): 定義在一對大括弧內。 只有這對大括弧內 (在它之後的) 程式碼可以使用它。 (副程式內或複合敘述內)

請複習基本概念中的 "變數宣告" 與 "變數定義" 的差別。

因為 C 語言裡面, 函數不可以層層相疊地定義, 所以函數只有兩種 scope: global scope (一般函數皆是) 及 file scope (以 static 宣告的函數)。

作業

請解釋以下敘述:

  1. 被呼叫好幾次的副程式當中的一個自動變數, 其實是好幾個名字相同, "生存年代" 不同的變數。
  2. 遞迴的副程式當中的一個自動變數, 其實是好幾個名字相同, 而且曾經同時存在的變數。
  3. Free store 中要來的變數一律沒有名字。

Preprocessing -- 前置處理

以 "#" 放在一列的最左邊開頭的 "句子" 叫做 preprocessor directive, 由 preprocessor 處理。

Preprocessor 完全看不懂 C 程式, 它所有的動作都純粹是字串的處理 (代換, 刪除, 插入, ...) 而已。

#if 的用途: 選擇性地編譯 C 本文檔的某部分, 例如避免重複編譯, 根據不同的作業平臺編譯不同的部分, ..。

#define 的用途: 定義簡單的函數 (還有助憶名。 簡單, 已看過, 不談)

  1. 使用時機: 函數很短, 不值得建立 activation record 所花的工夫, 例如絕對值, 最大值, 最小值, ..。
  2. 定義時要注意: 多用括弧, 將參數與結果都包起來。 例: #define hypotenuse(x, y) sqrt((x)*(x)+(y)*(y))
  3. 使用時要注意: 不要傳入有副作用的運算式, 例如: abs(x++)

注意: 不要把 preprocessing 和程式的流程 (例如 #if 與 if) 混淆了。 Preprocessing 只是字串處理。 Preprocessing 執行完了之後才要開始編譯而已, 程式根本還沒有開始執行!

從前置處理到程式執行的完整過程 從前置處理到程式執行的完整過程 (以 為例)

  1. Preprocessor 處理 preprocessor directive。
  2. Compiler 將程式原始碼編譯為 object code (.o 或 .obj, 目的碼)
  3. Librarian 將多個 object files 聯結產生靜態程式庫檔 (.a 或 .lib) 或產生動態程式庫檔 (.so 或 .dll)
  4. Linker 將一個或多個 object 檔與靜態程式庫檔聯結, 產生可執行檔 (在 UNIX 系統下可以是任何名稱; 在 MS 系統下為 .exe 或 .com)
  5. Loader 將可執行檔載入記憶體並與動態程式庫 (.so 或 .dll) 聯結, 開始執行。 為何要有動態程式庫? 動態分享, 減少可執行檔所佔硬碟及記憶體空間