總結:(源文件名為loops.c)
可以轉換到不同階段。階段1,將頭文件加進來。階段2,轉換成匯編程序,與機器類型相關。階段3,轉換成機器碼,但不完整。階段4,鏈接其他系統文件,形成最終可執行文件
cc -E -o loops-firststep.e loops.c
cc -S -o loops-second.s loops.c
cc -c loops.c#此時出現.o文件。或者,更可控地:cc -c -o loops-third.o loops.c
cc -o loops-final loops.c#鏈接成最終可執行文件
轉一:
The Four Stages of Compiling a C Program
Compiling a C program is a multi-stage process. At an overview level, the process can be split into four separate stages: Preprocessing, compilation, assembly, and linking.
In this post, I’ll walk through each of the four stages of compiling the following C program:
/* * "Hello, World!": A classic. */ #include <stdio.h> int main(void) { puts("Hello, World!"); return 0; }
Preprocessing#包含頭文件
The first stage of compilation is called preprocessing. In this stage, lines starting with a #
character are interpreted by the preprocessor as preprocessor commands. These commands form a simple macro language with its own syntax and semantics. This language is used to reduce repetition in source code by providing functionality to inline files, define macros, and to conditionally omit code.
Before interpreting commands, the preprocessor does some initial processing. This includes joining continued lines (lines ending with a \
) and stripping comments.
To print the result of the preprocessing stage, pass the -E
option to cc
:
cc -E hello_world.c
Given the “Hello, World!” example above, the preprocessor will produce the contents of the stdio.h
header file joined with the contents of the hello_world.c
file, stripped free from its leading comment:
[lines omitted for brevity]
extern int __vsnprintf_chk (char * restrict, size_t,
int, size_t, const char * restrict, va_list);
# 493 "/usr/include/stdio.h" 2 3 4
# 2 "hello_world.c" 2
int
main(void) {
puts("Hello, World!");
return 0;
}
Compilation#轉成機器相關的匯編語言
The second stage of compilation is confusingly enough called compilation. In this stage, the preprocessed code is translated to assembly instructions specific to the target processor architecture. These form an intermediate human readable language.
The existence of this step allows for C code to contain inline assembly instructions and for different assemblers to be used.
Some compilers also supports the use of an integrated assembler, in which the compilation stage generates machine code directly, avoiding the overhead of generating the intermediate assembly instructions and invoking the assembler.
To save the result of the compilation stage, pass the -S
option to cc
:
cc -S hello_world.c
This will create a file named hello_world.s
, containing the generated assembly instructions. On macOS 10.10.4, where cc
is an alias for clang
, the following output is generated:
.section __TEXT,__text,regular,pure_instructions
.macosx_version_min 10, 10
.globl _main
.align 4, 0x90
_main: ## @main
.cfi_startproc
## BB#0:
pushq %rbp
Ltmp0:
.cfi_def_cfa_offset 16
Ltmp1:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp2:
.cfi_def_cfa_register %rbp
subq $16, %rsp
leaq L_.str(%rip), %rdi
movl $0, -4(%rbp)
callq _puts
xorl %ecx, %ecx
movl %eax, -8(%rbp) ## 4-byte Spill
movl %ecx, %eax
addq $16, %rsp
popq %rbp
retq
.cfi_endproc
.section __TEXT,__cstring,cstring_literals
L_.str: ## @.str
.asciz "Hello, World!"
.subsections_via_symbols
Assembly#轉換成機器碼,生成.o文件
During this stage, an assembler is used to translate the assembly instructions to object code. The output consists of actual instructions to be run by the target processor.
To save the result of the assembly stage, pass the -c
option to cc
:
cc -c hello_world.c
Running the above command will create a file named hello_world.o
, containing the object code of the program. The contents of this file is in a binary format and can be inspected using hexdump
or od
by running either one of the following commands:
hexdump hello_world.o
od -c hello_world.o
Linking#鏈接缺失部分,成為真正可執行文件
The object code generated in the assembly stage is composed of machine instructions that the processor understands but some pieces of the program are out of order or missing. To produce an executable program, the existing pieces have to be rearranged and the missing ones filled in. This process is called linking.
The linker will arrange the pieces of object code so that functions in some pieces can successfully call functions in other ones. It will also add pieces containing the instructions for library functions used by the program. In the case of the “Hello, World!” program, the linker will add the object code for the puts
function.
The result of this stage is the final executable program. When run without options, cc
will name this file a.out
. To name the file something else, pass the -o
option to cc
:
cc -o hello_world hello_world.c
轉二:
GCC編譯器的基本選項如下表:
類型 | 說明 |
-E | 預處理后即停止,不進行編譯、匯編及連接 |
-S | 編譯后即停止,不進行匯編及連接 |
-c | 編譯或匯編源文件,但不進行連接 |
-o file | 指定輸出文件file |
C語言的include頭文件
- include是要告訴編譯器,包含頭文件
- 在C語言中,任何的庫函數調用都需要包含頭文件
- 頭文件也相當於一個文檔聲明
- 如果把main函數放在第一個文件中,而把自定義函數放在第二個文件中,那么就需要在第一個文件中聲明函數原型
- 如果把函數原型包含在一個頭文件中,那么就不用每次使用函數的時候都聲明其原型了,把函數聲明放進頭文件中是個好習慣!
- 頭文件可以不需要編譯
- 可以查看具體的聲明
- 頭文件加上實現文件的o文件提交給使用者即可,不需要知道源代碼
- o文件預先編譯,所以整個項目編譯時,會大大提高編譯的時間 。
- 當一個文件(A.c文件)依賴於頭文件(b.h)時,如果b.c編譯之后形成的b.o文件重新編譯后,a.o的文件不需要重新編譯
- 可以極大降低手工復制,粘貼的錯誤幾率
頭文件的注意事項:
- <頭文件>,表示讓C語言編譯器在系統目錄(即gcc編譯器的include目錄下)下尋找相關的頭文件
- “頭文件”,表示讓C語言編譯器在用戶當前目錄下尋找相關的頭文件
- 如果是使用了C語言庫函數的需要的頭文件,那么一定是#include<>
- 如果是使用了用戶自定義的頭文件,那么一定是#include“”
轉三:
C語言編譯和鏈接詳解(通俗易懂,深入本質)
我們平時所說的程序,是指雙擊后就可以直接運行的程序,這樣的程序被稱為可執行程序(Executable Program)。在 Windows 下,可執行程序的后綴有.exe
和.com
(其中.exe
比較常見);在類 UNIX 系統(Linux、Mac OS 等)下,可執行程序沒有特定的后綴,系統根據文件的頭部信息來判斷是否是可執行程序。
可執行程序的內部是一系列計算機指令和數據的集合,它們都是二進制形式的,CPU 可以直接識別,毫無障礙;但是對於程序員,它們非常晦澀,難以記憶和使用。
例如,在屏幕上輸出“VIP會員”,C語言的寫法為:
puts("VIP會員");
二進制的寫法為:

你感受一下,直接使用二進制是不是想撞牆,是不是受到一噸重的傷害?
在計算機發展的初期,程序員就是使用這樣的二進制指令來編寫程序的,那個拓荒的年代還沒有編程語言。
直接使用二進制指令編程對程序員來說簡直是噩夢,尤其是當程序比較大的時候,不但編寫麻煩,需要頻繁查詢指令手冊,而且除錯會異常苦惱,要直接面對一堆二進制數據,讓人眼花繚亂。另外,用二進制指令編程步驟繁瑣,要考慮各種邊界情況和底層問題,開發效率十分低下。
這就倒逼程序員開發出了編程語言,提高自己的生產力,例如匯編、C語言、C++、Java、Python、Go語言等,都是在逐步提高開發效率。至此,編程終於不再是只有極客能做的事情了,不了解計算機的讀者經過一定的訓練也可以編寫出有模有樣的程序。
編譯(Compile)
C語言代碼由固定的詞匯按照固定的格式組織起來,簡單直觀,程序員容易識別和理解,但是對於CPU,C語言代碼就是天書,根本不認識,CPU只認識幾百個二進制形式的指令。這就需要一個工具,將C語言代碼轉換成CPU能夠識別的二進制指令,也就是將代碼加工成 .exe 程序的格式;這個工具是一個特殊的軟件,叫做編譯器(Compiler)。
編譯器能夠識別代碼中的詞匯、句子以及各種特定的格式,並將他們轉換成計算機能夠識別的二進制形式,這個過程稱為編譯(Compile)。
編譯也可以理解為“翻譯”,類似於將中文翻譯成英文、將英文翻譯成象形文字,它是一個復雜的過程,大致包括詞法分析、語法分析、語義分析、性能優化、生成可執行文件五個步驟,期間涉及到復雜的算法和硬件架構。對於學計算機或者軟件的大學生,“編譯原理”是一門專業課程,有興趣的讀者請自行閱讀《編譯原理》一書,這里我們不再展開講解。
注意:不了解編譯原理並不影響我們學習C語言,我也不建議初學者去鑽研編譯原理,貪多嚼不爛,不要把自己繞進去。
C語言的編譯器有很多種,不同的平台下有不同的編譯器,例如:
- Windows 下常用的是微軟開發的 Visual C++,它被集成在 Visual Studio 中,一般不單獨使用;
- Linux 下常用的是 GUN 組織開發的 GCC,很多 Linux 發行版都自帶 GCC;
- Mac 下常用的是 LLVM/Clang,它被集成在 Xcode 中(Xcode 以前集成的是 GCC,后來由於 GCC 的不配合才改為 LLVM/Clang,LLVM/Clang 的性能比 GCC 更加強大)。
你的代碼語法正確與否,編譯器說了才算,我們學習C語言,從某種意義上說就是學習如何使用編譯器。
編譯器可以 100% 保證你的代碼從語法上講是正確的,因為哪怕有一點小小的錯誤,編譯也不能通過,編譯器會告訴你哪里錯了,便於你的更改。
鏈接(Link)
C語言代碼經過編譯以后,並沒有生成最終的可執行文件(.exe 文件),而是生成了一種叫做目標文件(Object File)的中間文件(或者說臨時文件)。目標文件也是二進制形式的,它和可執行文件的格式是一樣的。對於 Visual C++,目標文件的后綴是.obj
;對於 GCC,目標文件的后綴是.o
。
目標文件經過鏈接(Link)以后才能變成可執行文件。既然目標文件和可執行文件的格式是一樣的,為什么還要再鏈接一次呢,直接作為可執行文件不行嗎?
不行的!因為編譯只是將我們自己寫的代碼變成了二進制形式,它還需要和系統組件(比如標准庫、動態鏈接庫等)結合起來,這些組件都是程序運行所必須的。
鏈接(Link)其實就是一個“打包”的過程,它將所有二進制形式的目標文件和系統組件組合成一個可執行文件。完成鏈接的過程也需要一個特殊的軟件,叫做鏈接器(Linker)。
隨着我們學習的深入,我們編寫的代碼越來越多,最終需要將它們分散到多個源文件中,編譯器每次只能編譯一個源文件,生成一個目標文件,這個時候,鏈接器除了將目標文件和系統組件組合起來,還需要將編譯器生成的多個目標文件組合起來。
再次強調,編譯是針對一個源文件的,有多少個源文件就需要編譯多少次,就會生成多少個目標文件。
總結
不管我們編寫的代碼有多么簡單,都必須經過「編譯 --> 鏈接」的過程才能生成可執行文件:
- 編譯就是將我們編寫的源代碼“翻譯”成計算機可以識別的二進制格式,它們以目標文件的形式存在;
- 鏈接就是一個“打包”的過程,它將所有的目標文件以及系統組件組合成一個可執行文件。
轉四:
linker input file unused because linking not done
GCC是Linux平台下常用的編譯鏈接器。編譯鏈接的過程分為:
源代碼-->預處理文件(.i)-->編譯后的匯編代碼(.s)-->匯編后的二進制文件(.o)-->鏈接后的二進制物件(無后綴)。
處理程序分別是 :cpp、ccl、as、ld。
使用 -v選項,可以看到各個階段關聯的處理程序。
使用gcc -E 指示gcc對源代碼進行預處理,結果直接輸出到終端。
使用gcc -S 指示gcc編譯成為匯編語言
使用gcc -c 指示gcc直至形成二進制文件(不進行鏈接)
使用gcc 指示gcc鏈接形成二進制物件(多個二進制模塊鏈接形成大的模塊或者可執行程序)
因此你需要的目標文件的種類是 -E、-S、-c或者不帶這些參數確定的,
源文件可以是中間文件的一種。-o參數控制的僅僅是輸出文件的名稱。
但是gcc默認會根據源文件的后綴去判斷應該調用處理程序的那些。例如源文件的后綴是.c,則gcc -E使用的是cpp,gcc -c則使用cpp、ccl、as。如果源文件的后綴是.o,則gcc -E 是無法進行的,會報錯:linker input file unused because linking not done。這是gcc發現這個應該進行鏈接,但是選項指示不使用linker程序,因而報這種錯誤信息。如果源文件是二進制的文件,但是保存的源文件后綴卻是.c,則gcc會當作這是.c文件,如果采用gcc不帶參數,則gcc會很多錯,因為它把這個文件當作源代碼處理的。
注意:這里,源文件指的是gcc的輸入文件,源代碼指的是程序源代碼文件。