天天看點

gcc 學習筆記(一) - 編譯C程式 及 編譯過程一. C程式編譯過程二. 編譯C程式

編譯過程簡介 : c語言的源檔案 編譯成 可執行檔案需要四個步驟, 預處理 (preprocessing) 擴充宏, 編譯 (compilation) 得到彙編語言, 彙編 (assembly) 得到機器碼, 連接配接 (linking) 得到可執行檔案;

-- 檢視每個步驟的編譯細節 : "-e" 對應 預處理, "-s" 對應 編譯, "-c" 對應 彙編, "-o" 對應 連接配接;

-- 每個步驟對應的工具 : 預處理器 (cpp - the c preprogressor), 編譯器 (cc1), 彙編器 (as), 連接配接器 (ld);

-- 檢視總體編譯細節 : 使用 "-v" 參數, 可以檢視總體編譯細節;

預處理指令 : 源程式中 以 "#" 開頭的指令是 預處理指令, 如 "#include", "#define", "ifndef" 等;

預處理過程 : 預處理将 include 的檔案插入到 源檔案中, 展開 define 宏定義, 根據條件 編譯代碼;

編譯下面的源程式 : 

預處理結果 : 預處理 源程式 産生的結果會放到 ".i" 字尾的檔案中, 預設情況下 ".i" 字尾檔案是不寫到磁盤中的, 如果加上 "-save-temps" 參數, 就會将所有的中間檔案都儲存到磁盤中;

-- 分析下面的例子 : 使用 gcc -save-temps main.c 指令編譯源程式, 所有的中間檔案都會保留, main.i 是預處理結果, main.s 是編譯結果, main.o 是彙編結果, a.out 是連接配接生成的可執行檔案;

檢視預處理細節 : 使用 gcc -e mian.c 指令, 會輸出編譯細節, 列印出上千行, 這裡隻貼出部分;

在 gcc 指令行中進行宏定義 : 使用 gcc -dnum=5 main.c 指令, 在程式中就可以使用 num 宏定義了, "-dnum" 相當于在程式中定義了 "#define num 5";

-- main.c 内容 : 

-- 編譯過程 : 

編譯流程 : 編譯器在編譯階段依次執行 詞法分析, 文法分析, 代碼優化, 存儲配置設定, 代碼生成 五個步驟;

-- 多次掃描方案 : 編譯器每次掃描代碼隻完成一項工作, 如 第一次掃描 隻進行詞法分析, 第二次掃描進行 文法分析, 掃描多次完成上面的五個步驟;

生成中間的彙編中間檔案 : 使用 gcc -s main.c 編譯上面的 main.c 源程式, 可以得到 mian.s 彙編語言檔案, 這是産生的中間彙程式設計式;

-- 編譯過程 及 結果 : 

彙編過程 : 彙編 就是将 彙編語言代碼 翻譯成 機器碼, 也就是 ".o" 字尾的對象檔案, 該過程 使用 彙編器 as 實作;

擷取中間檔案 : "-c" 選項可以保留 彙編過程中的 ".o" 字尾的中間檔案, 使用 gcc -c main.c 指令, 可以獲得 main.o 對象檔案;

連結過程 : 使用 ld 連接配接器, 将 彙編 過程中生成的 ".o" 對象檔案, 與其它 對象檔案 和 庫檔案連接配接起來, 生成可執行的二進制檔案;

連接配接示例 : 使用 gcc main.o 将彙編過程生成的對象檔案 main.o , 生成可執行檔案 a.out ;

c語言程式示例 : 簡單的hello world;

簡單編譯 : 使用 gcc main.c 指令, 會生成 a.out 可執行檔案, 使用 ./a.out 可以執行編譯好的c程式;

指定輸出檔案編譯 : 如果不想使用 a.out 作為輸出檔案, 可以使用 -o 參數指定輸出檔案, 如果該檔案存在就會覆寫;

-- 指令 : gcc main.c -o main;

顯示警告選項 : -wall 選項, 可以在編譯的時候, 将警告資訊輸出到終端中;

-- 編譯輸出警告資訊 : gcc -wall main.c;

人為制造警告 : 在 printf 輸出的時候, 使用 %s 作為一個 int 資料的占位符;

-- 執行編譯 : gcc -wall main.c, 編譯的時候報出警告, 但是編譯通過, 但是運作的時候就出錯了;

由三個檔案組成的程式 : kill.h, kill.c, main.c, 其中 main.c 是主函數入口, 調用 kill.c 定義的方法;

-- kill.h 内容 : 聲明 kill 方法, 引用了該頭檔案, 即可使用 kill 方法;

-- kill.c 内容 : 主要實作 kill.h 中聲明的 kill 方法;

-- mian.c内容 : 引用 kill.h 庫;

引用頭檔案庫符号差別 : #include"kill.h" #include<kill.h> ;

-- #include "kill.h" : 先在目前目錄搜尋 kill.h 頭檔案, 在到系統中搜尋該頭檔案;

-- #include <kill.h> : 直接去系統庫中尋找頭檔案, 不會搜尋目前目錄;

編譯檔案 : 使用 gcc -wall main.c kill.c -o kill 進行編譯;

開發需求 : 當一個項目比較大的時候, 整個項目編譯時間會很長, 如果改變一個函數就需要重新編譯整個項目, 就會很浪費時間;

-- 解決方案 : 程式被存儲在多個源檔案中, 每個源檔案都單獨進行編譯;

單獨編譯多個源檔案步驟 :  首先生成 對象檔案, 再将對象檔案連結生成可執行檔案;

-- 編譯對象檔案 : 将源程式編譯成不可執行的檔案, 生成 .o 字尾的對象檔案;

-- 連結程式 : gcc 中有一個連結器将所有的對象檔案連結到一起, 生成一個可執行檔案;

解析對象檔案 : 檔案中存放的是機器碼, 機器碼中對其他檔案中的 函數 或者 變量引用的位址沒有解析, 當連結程式的時候才将這些位址寫入;

生成對象檔案 : -c 參數用于生成 對象檔案;

-- 生成kill.o對象檔案 : gcc -wall -c kill.c , 會生成 kill.o 檔案, 該對象檔案中引用 kill 方法, 該方法對應的位址沒有被解析;

-- 生成 main.o對象檔案 : gcc -wall -c mian.c, 生成 main.o 檔案;

連結對象檔案 : gcc main.o kill.o -o main 指令, 連結 main.o 和 kill.o 兩個對象檔案;

-- 不許要-wall參數 : 連結程式隻有兩種結果, 成功 或者 失敗, 不許要警告資訊了;

-- 連結器 : gcc中ld連結器 用來連結對象檔案;

對象檔案的連結次序 : 大部分編譯器都可以随意排列順序, 但是有的編譯器需要注意連結次序;

-- 編譯器和連接配接器次序 : 編譯器和連結器搜尋外部函數 是 從左到右進行查找;

-- 檔案次序 : 調用函數的 對象檔案, 該檔案應該先于 定義函數的 對象檔案, 這裡 main.o 應該在 kill.o 之前;

-- 錯誤排查 : 如果在編譯程式的時候, 列出了所有的檔案, 但是還出現了 未定義 錯誤, 就需要注意 檔案排列的問題;

修改檔案流程 : 當修改了一個檔案之後, 隻需要 重新編譯這個檔案即可, 之後将這個新編譯的對象檔案 與 原來的對象檔案進行連結, 即可生成新的可執行檔案;

-- 重新編譯 : 當修改了一個檔案之後, 隻需要将這個檔案重新編譯成 對象檔案即可;

-- 重新連結 : 将新編譯的對象檔案, 與之前已經編譯好的 其它源檔案的對象檔案進行連結即可;

,

繼續閱讀