乙個c語言程式從源**檔案變成最後的可執行程式檔案,需要經歷預處理、編譯、彙編、鏈結四個過程。
預處理:條件編譯、標頭檔案包含、巨集替換,生成.i檔案
編譯:將預處理後的檔案轉換成組合語言,生成.s檔案
彙編:彙編變為目標**(機器**)生成.o檔案
日常使用gcc編譯器完成上述過程,需要以下選項:
-e preprocess only; do not compile, assemble or link
-s compile only; do not assemble or link
-c compile and assemble, but do not link
-o place the output into
#include#define file_name "hello.c"
int main()
else
return 0;
}
gcc -e hello.c -o hello.i
gcc引數-e表示只進行預處理
cat hello.i
我們可以看到預處理後的**
......
extern file *popen (const char *__command, const char *__modes) ;
extern int pclose (file *__stream);
extern char *ctermid (char *__s) __attribute__ ((__nothrow__ , __leaf__));
# 912 "/usr/include/stdio.h" 3 4
extern void flockfile (file *__stream) __attribute__ ((__nothrow__ , __leaf__));
extern int ftrylockfile (file *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
extern void funlockfile (file *__stream) __attribute__ ((__nothrow__ , __leaf__));
# 942 "/usr/include/stdio.h" 3 4
# 2 "hello.c" 2
# 5 "hello.c"
int main()
else
return 0;
}
通過對hello.i檔案檢視,原本幾行的**變成了幾百行,而且include關鍵字也不見了,取而代之的是stdio.h的路徑和內容,可以發現預處理過程完成巨集的替換、注釋的消除、標頭檔案的展開等。
預處理完成的工作
將所有的#define刪除,並且展開所有的巨集定義
處理所有的條件預編譯指令,比如#if #ifdef #elif #else #endif等
處理#include 預編譯指令,將被包含的檔案插入到該預編譯指令的位置。
刪除所有注釋 「//」和」/* */」.
新增行號和檔案標識,以便編譯時產生除錯用的行號及編譯錯誤警告行號。
保留所有的#pragma編譯器指令,因為編譯器需要使用它們
gcc -s hello.i -o hello.s
gcc引數 -s表示只進行編譯過程,不進行彙編和鏈結
cat hello.s
.file "hello.c"
.section .rodata
.lc0:
.string "hello.c"
.lc1:
.string "%s,case 1 hello world\n"
.lc2:
.string "%s,case other hello world\n"
.text
.globl main
.type main, @function
.file "hello.c"
.section .rodata
.lc0:
.string "hello.c"
.lc1:
.string "%s,case 1 hello world\n"
.lc2:
.string "%s,case other hello world\n"
.text
.globl main
.type main, @function
main:
......
我們可以看到hello.s的檔案內容是優化後的彙編**。
編譯過程就是把預處理完的檔案進行一系列的詞法分析,語法分析,語義分析及優化後生成相應的彙編**。
組合語言是彙編指令集、偽指令集和使用它們規則的統稱,使用具有一定含義的符號為助憶符,用指令助憶符、符號位址等組成的符號指令稱為彙編格式指令。
簡單的可以理解為組合語言是一本詞典,01100101011010這樣的二進位制字串是單詞,彙編指令是單詞的含義。計算機能讀懂二進位制字串,而人能讀懂的是翻譯過來的彙編指令
gcc -c hello.s -o hello.o
gcc引數-c表示彙編,將彙編檔案編譯成目標**(機器**,二進位制101010110)
cat hello.o
^?elf^b^a^a^@^@^@^@^@^@^@^@^@^a^@>^@^a^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@`^c^@^@^@^@^@^@^@^@^@^@@^@^@^@^@^@@^@^m^@
^@uh<89>åh<83>ì^pçeü^a^@^@^@<83>}ü^@t^v¾^@^@^@^@¿^@^@^@^@¸^@^@^@^@è^@^@^@^@ë^t¾^@^@^@^@¿^@^@^@^@¸^@^@^@^@è^@^@^@^@¸^@^@^@^@éãhello.c^@%s,case 1 hello world
^@%s,case other hello world
^@^@gcc: (ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0
可以看到列印許多不可見的字元,原因是目標已經是二進位制格式的,不同於源**(文字格式)
彙編器是將彙編**轉變成機器可以執行的命令,每乙個彙編語句幾乎都對應一條機器指令。彙編相對於編譯過程比較簡單,根據彙編指令和機器指令的對照表一一翻譯即可。
。對於被翻譯系統處理的每乙個c語言源程式,都將最終經過這一處理而得到相應的目標檔案。
目標檔案中所存放的也就是與源程式等效的目標的機器語言**。
匯程式設計序生成的目標檔案實際上是可重定位檔案,它其中包含有適合於其它目標檔案鏈結來建立乙個可執行的或者共享的目標檔案的**和資料。
也就是說,由匯程式設計序生成的目標檔案並不能立即就被執行,它並非最終可執行的二進位制序列,因為其中可能還有許多沒有解決的問題。那麼這個就是鏈結程式的工作了。
gcc hello.s -o hello
通過呼叫鏈結器ld來鏈結程式執行需要的一大堆目標檔案,以及所依賴的其它庫檔案,最後生成可執行檔案。
例如hello.c程式中使用c標準庫中的printf,在鏈結階段就需要將彙編生成的目標機器**與對應的庫繫結在一起。使用ldd命令即可以檢視可執行程式鏈結的庫。
ldd hello
cmh@ubuntu:~/project/gdb$ ldd hello
linux-vdso.so.1 => (0x00007fff6ab28000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f1ed58d4000)
/lib64/ld-linux-x86-64.so.2 (0x00007f1ed5c9e000)
靜態鏈結是指在編譯階段直接把靜態庫加入到可執行檔案中去,這樣可執行檔案會比較大。
而動態鏈結則是指鏈結階段僅僅只加入一些描述資訊,而程式執行時再從系統中把相應動態庫載入到記憶體中去。
-rwxrwxr-x 1 cmh cmh 8608 oct 13 20:49 hello*
-rw-rw-r-- 1 cmh cmh 285 oct 13 19:49 hello.c
-rw-rw-r-- 1 cmh cmh 17228 oct 13 19:50 hello.i
-rw-rw-r-- 1 cmh cmh 1696 oct 13 20:19 hello.o
-rw-rw-r-- 1 cmh cmh 714 oct 13 20:03 hello.s
可以發現hello.o二進位制目標檔案只有1.6k,hello可執行檔案大小為8k多,這就是鏈結過程鏈結的庫檔案。 C程式編譯過程
題記 前幾天去華為面試實習生,面試官問了個問題,讓我說出乙個程式的詳細編譯過程,當時磕磕絆絆說了一堆東西,事後自己都不知道當時說了什麼,慚愧。c語言編譯過程 編譯,編譯程式讀取源程式 字元流 對之進行詞法和語法的分析,將高階語言指令轉換為功能等效的彙編 再由匯程式設計序轉換為機器語言,並且按照作業系...
C 程式編譯過程
首先是編譯過程整體簡介 編譯過程主要分為 4個過程 1 編譯預處理 預編譯程式完成的工作,可以說成是對源程式的 替換 工作。經過這個過程,生成乙個沒有巨集定義 沒有條件編譯指令 沒有特殊符號的輸出檔案。2 編譯 優化階段 通過詞法分析 語法分析,在確認所有的指令都符合語法規則之後,將其翻譯成等價的中...
C程式編譯過程
gcc編譯c 會有四個階段 預處理 將c 中的標頭檔案和巨集進行處理 彙編 把彙編 轉化成機器指令,並以特定的二進位制格式輸出儲存在 o這樣的目標檔案中 流程圖 參考閱讀 3.c程式分析 gcc e hello.c o hello.i 預處理 gcc s hello.i o hello.s 編譯 g...