認識編譯器和C/C++編譯

一. 編譯器

編譯器也是一種程序,其作用是將一種語言翻譯為另一種語言,通常是將高級語言翻譯為低級語言,或者說是將源代碼翻譯成能被計算機或虛擬機執(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)鏈接庫是干什么的

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容