一. 編譯器
編譯器也是一種程序,其作用是將一種語言翻譯為另一種語言,通常是將高級語言翻譯為低級語言,或者說是將源代碼翻譯成能被計算機或虛擬機執(zhí)行的目標代碼。
編譯器的主要工作流程是:源代碼-預(yù)處理器-編譯器-目標代碼-鏈接器-可執(zhí)行文件
另一個角度的工作流程:詞法分析-語法分析-語義分析-中間代碼生成-代碼優(yōu)化-目標代碼生成-目標代碼優(yōu)化
編譯器的種類
“本地”編譯器
用來生成與編譯器本身所在環(huán)境操作系統(tǒng)(平臺)相同的環(huán)境運行的目標代碼的編譯器叫“本地”編譯器。
“交叉”編譯器
生成用來在其他平臺上運行的目標代碼,這種編譯器叫做“交叉”編譯器。這個過程也叫交叉編譯。
例如:在 Mac 上編譯能在 Android 上運行的 so , 這個過程就屬于交叉編譯。
編譯器的前后端
以中間代碼生成步驟為中心劃分:
- 與源語言有關(guān),與目標語言無關(guān)的部分叫做編譯器前端
- 和目標語言有關(guān),和源語言無關(guān)的部分叫做編譯器后端
將編譯器分為前端和后端,對編譯技術(shù)的起到了一定作用。
二. GNU & GCC & Clang & llvm
1. GNU
GNU, Gnu's Not Unix 的縮寫。由于一開始 Unix 系統(tǒng)是商業(yè)收費的,理查德·斯托曼提出 GNU 計劃,希望發(fā)展出一套完整的開放源代碼操作系統(tǒng)來取代 Unix,名為 GNU 。
1989 年,GNU 項目中編輯器、編譯器、shell 等都已完成,唯獨缺了操作系統(tǒng)核心,所以開始正式發(fā)展 Hurd 來作為 GNU 計劃的操作系統(tǒng)。
1991 年,Linux 出現(xiàn), GNU 項目的軟件可在 Linux 上運行。
1992 年,Linux 和 GNU 結(jié)合,形成完全自由的操作系統(tǒng),稱為 GNU/Linux 簡稱 Linux 。此時 Hurd 還沒完善,被拋棄。
2. GCC
gcc, GNU C Compiler 的縮寫,是 GNU 項目的編譯器部分,也是類 Unix 和 Mac OS X 操作系統(tǒng)的標準編譯器。
gcc 原本只處理 C 語言,后來也發(fā)展成可處理 Object-c、Java 、C++。
g++
gcc 和 g++ 都是 GNU 的編譯器。他們的區(qū)別如下:
- 對于 .c ,gcc 把它當(dāng)作 C 程序,而 g++ 當(dāng)作 C++ 程序;對于 .cpp , gcc 和 g++ 都會當(dāng)作 c++ 程序。
對于 .cpp 的編譯鏈接
gcc 和 g++ 都可以編譯,而鏈接可以用 g++ 或者gcc -lstdc++。因為 gcc 命令不能自動和 C++ 程序使用的庫聯(lián)接,所以通常使用 -lstdc++ 來完成聯(lián)接。
3. Clang
Clang 是一個 C、C++、Objective-C 和 Objective-C++ 編程語言的編譯器前端。它采用了底層虛擬機(LLVM)作為其后端。這個軟件項目是由蘋果發(fā)起的,目標是替代 GNU 的 gcc 編譯器套裝。
因為 gcc 的編譯器慢慢無法滿足蘋果的需求,因此,蘋果開發(fā)了 Clang 與LLVM來完全取代 gcc,Xcode4 之后,蘋果的默認編譯器已經(jīng)是 Clang/LLVM 。Clang 作為編譯器前端,LLVM 作為編譯器后端。
4. MinGw
MinGw 是 Minimalist GNU for Windows 的縮寫。它是一個可自由使用和自由發(fā)布的 Windows 特定頭文件和使用 GNU 工具集導(dǎo)入庫的集合,允許在 Windows 平臺生成本地的 Windows 程序而不需要第三方 C 運行時(C Runtime)庫。
三. C/C++ 編譯過程
C/C++ 編譯過程可以分為四個步驟:
- 預(yù)處理
- 編譯
- 匯編
- 鏈接
1.預(yù)處理
預(yù)處理是處理文件中的預(yù)處理命令,通常是 # 開頭,預(yù)處理通常包含如下步驟:
- 替換宏定義 #define
- 處理條件預(yù)編譯指令如 #if
- 處理 #include 指令,將需要包含的文件遞歸包含進來
- 等等
可以使用 -E 命令執(zhí)行預(yù)處理操作,-o 表示生成的文件,最終生成 .i 文件
gcc -E -o test.i test.c
例如:有 test.h test.c 文件
>>test.h
int func(int a,int b) {
return a + b;
}
>>test.c
#include "test.h"
#define A 1
#define B 2
int main() {
int c = func(A,B);
}
執(zhí)行 gcc -E -o test.i test.c
>> 生成的 test.i
# 1 "test.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 363 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "test.c" 2
# 1 "./test.h" 1
int func(int a,int b) {
return a + b;
}
# 3 "test.c" 2
int main() {
int c = func(1,2);
}
2. 編譯
編譯的過程是將經(jīng)過處理之后的程序轉(zhuǎn)換成特定匯編語言代碼的過程,可以使用 -E 命令執(zhí)行編譯操作,其表示讓編譯器編譯之后停止,不進行后續(xù)步驟。
gcc -S -o test.s test.c
>> 匯編代碼:
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 10, 15 sdk_version 10, 15, 4
.globl _func ## -- Begin function func
.p2align 4, 0x90
_func: ## @func
.cfi_startproc
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
movl -4(%rbp), %eax
addl -8(%rbp), %eax
popq %rbp
retq
.cfi_endproc
## -- End function
.globl _main ## -- Begin function main
.p2align 4, 0x90
_main: ## @main
.cfi_startproc
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
subq $16, %rsp
movl $1, %edi
movl $2, %esi
callq _func
xorl %ecx, %ecx
movl %eax, -4(%rbp)
movl %ecx, %eax
addq $16, %rsp
popq %rbp
retq
.cfi_endproc
## -- End function
.subsections_via_symbols
3.匯編
匯編是將上述匯編代碼轉(zhuǎn)換為機器碼,這一步產(chǎn)生的文件叫做目標文件,是二進制格式。每一個源文件都會產(chǎn)生一個 .o 的目標文件。
as test.s -o test.o
等價于
gcc –c test.c –o test.o
>> 這里就不貼
4.鏈接
鏈接過程是將目標文件以及所需的庫文件,鏈接成最終的 .out 可執(zhí)行文件。鏈接程序的主要工作就是將有關(guān)的 .o 的目標文件彼此相連接,使得所有的這些目標文件成為一個能夠被操作系統(tǒng)裝入執(zhí)行的統(tǒng)一整體。
>> 執(zhí)行
gcc test.c
生成 a.out 的可執(zhí)行文件
在 mac 上執(zhí)行 ./a.out 可運行文件
四. 靜態(tài)鏈接和動態(tài)鏈接
靜態(tài)鏈接
靜態(tài)鏈接:在鏈接階段,將匯編生成的目標文件 .o 與引用到的庫一起鏈接打包進可執(zhí)行文件中。就是在編譯鏈接時直接將需要的執(zhí)行代碼拷貝到調(diào)用處,因此允許的時候存在多份內(nèi)存拷貝。
靜態(tài)鏈接庫:一組目標文件的的集合,即很多目標文件經(jīng)過壓縮后打包形成一個文件 .a
靜態(tài)鏈接庫的特點:
- 靜態(tài)庫對函數(shù)庫的鏈接是放在編譯時期完成的
- 程序在運行的時候與函數(shù)庫無關(guān)
- 浪費內(nèi)存,多個地方調(diào)用某個函數(shù)就存在多次拷貝
動態(tài)鏈接
就是在編譯的時候不直接拷貝可執(zhí)行的代碼,而是通過記錄一系列符號和參數(shù),在程序運行或加載時將這些信息傳遞給操作系統(tǒng),操作系統(tǒng)負責(zé)將需要的動態(tài)庫加載到內(nèi)存中。
然后程序在運行到指定的代碼時,去共享執(zhí)行內(nèi)存中已經(jīng)加載的動態(tài)庫可執(zhí)行代碼,最終達到運行時連接的目的。
動態(tài)鏈接庫的特點:
- 動態(tài)庫把對一些庫函數(shù)的鏈接載入推遲到程序運行的時期。
- 多個程序可以共享同一段代碼,而不需要在內(nèi)存上存儲多個拷貝,
- 缺點是由于是運行時加載,可能會影響程序的前期執(zhí)行性能。
不同操作系統(tǒng)下編譯過程文件的后綴
| 系統(tǒng) | 源文件 | 目標文件 | 動態(tài)鏈接庫 | 靜態(tài)鏈接庫 | 可執(zhí)行文件 |
|---|---|---|---|---|---|
| windows | .c/.cpp | .obj | .dll | .lib | .exe |
| Linux | .c/.cpp | .o | .so | .a | .out/coff/elf (沒有后綴) |
| Mac OS X | .c/.cpp | .o | .dylib | .a | .out/coff/elf (沒有后綴) |
參考文章
編譯器
編譯器(GNU & GCC & clang & llvm)
C語言編譯過程詳解
Linux 中的動態(tài)鏈接庫和靜態(tài)鏈接庫是干什么的