3. 編譯器gcc/g++
3.1 背景知識
1. 預處理(進行宏替換/去注釋/條件編譯/頭文件展開等)
2. 編譯(生成匯編)
3. 匯編(生成機器可識別代碼)
4. 連接(生成可執行文件或庫文件)
3.2?gcc編譯選項
格式 : gcc 【選項】 要編譯的文件 【選項】【目標文件】
1.預處理(進行宏替換)
預處理功能主要包括宏定義,?件包含,條件編譯,去注釋等。
預處理指令是以#號開頭的代碼行。
實例: gcc –E hello.c –o hello.i
選項“-E”,該選項的作?是讓 gcc 在預處理結束后停?編譯過程。
選項“-o”是指?標?件,“.i”?件為已經過預處理的C原始程序。
2. 編譯(生成匯編)
在這個階段中,gcc ?先要檢查代碼的規范性、是否有語法錯誤等,以確定代碼的實際要做的?作,在檢查?誤后,gcc 把代碼翻譯成匯編語?。
用戶可以使?“-S”選項來進?查看,該選項只進?編譯?不進?匯編,?成匯編代碼。實例: gcc –S hello.i –o hello.s
?
3. 匯編(生成機器可識別代碼)
匯編階段是把編譯階段?成的“.s”?件轉成?標?件
讀者在此可使?選項“-c”就可看到匯編代碼已轉化為“.o”的?進制?標代碼了
實例: gcc –c hello.s –o hello.o
4. 連接(生成可執行文件或庫文件)
3.3 動態鏈接和靜態鏈接
在我們的實際開發中,不可能將所有代碼放在?個源?件中,所以會出現多個源?件,?且多個源?件之間不是獨?的,?會存在多種依賴關系,如?個源?件可能要調?另?個源?件中定義的函數,但是每個源?件都是獨?編譯的,即每個*.c?件會形成?個*.o?件,為了滿?前?說的依賴關系,則需要將這些源?件產?的?標?件進?鏈接,從?形成?個可以執?的程序。這個鏈接的過程就是靜態鏈接。靜態鏈接的缺點很明顯:
- 浪費空間:因為每個可執?程序中對所有需要的?標?件都要有?份副本,所以如果多個程序對同?個?標?件都有依賴,如多個程序中都調?了printf()函數,則這多個程序中都含有
printf.o,所以同?個?標?件都在內存存在多個副本;
-
更新?較困難:因為每當庫函數的代碼修改了,這個時候就需要重新進?編譯鏈接形成可執?程序。但是靜態鏈接的優點就是,在可執?程序中已經具備了所有執?程序所需要的任何東西,在執?的時候運?速度快
動態鏈接的出現解決了靜態鏈接中提到問題。動態鏈接的基本思想是把程序按照模塊拆分成各個相對獨?部分,在程序運?時才將它們鏈接在?起形成?個完整的程序,?不是像靜態鏈接?樣把所有程序模塊都鏈接成?個單獨的可執??件。
動態鏈接其實遠?靜態鏈接要常?得多。?如我們查看下 hello 這個可執?程序依賴的動態庫,會發現它就?到了?個c動態鏈接庫:
$ ldd hello
linux-vdso.so.1 => (0x00007fffeb1ab000)
libc.so.6 => /lib64/libc.so.6 (0x00007ff776af5000)
/lib64/ld-linux-x86-64.so.2 (0x00007ff776ec3000)# ldd命令?于打印程序或者庫?件所依賴的共享庫列表。
在這里涉及到?個重要的概念: 庫!!!
我們的C程序中,并沒有定義“printf”的函數實現,且在預編譯中包含的“stdio.h”中也只有該函數的聲明,?沒有定義函數的實現,那么,是在哪?實“printf”函數的呢?
答案是:系統把這些函數實現都被做到名為 libc.so.6 的庫文件中去了,在沒有特別指定時,gcc 會到系統默認的搜索路徑“/usr/lib”下進行查找,也就是鏈接到 libc.so.6 庫函數中去,這樣就能實現函數“printf”了,而這也就是鏈接的作用。
如何理解庫?
為什么要有庫?讓程序員直接使用,提高開發效率,語言上的庫更多的時提供公共的方法集合(c語言提供的printf需要stdio.h, c++提供STL容器 需要iostream),printf(“hello world” )是打印到了顯示器硬件上,是c語言工程師把printf往顯示器上打印的功能寫好了把他放在庫里面你直接調用;使你不用外部的東西直接在語言當中就能編程使用語言提供的方法能在語言層面調用printf,STL容器之類)把搭建上層應用所需要的所有底層功能全部按技術角度提前寫好放入庫,讓程序員直接使用,提高開發效率。
動態庫/共享庫最終也是會加載到內存中的,只有一份,把公共的方法抽取出來,在系統中只有一份節省內存資源,磁盤里面不存速度慢一點。
?動態鏈接就是程序還沒加載到內存中時,就已經和動態庫建立了連接;
動態鏈接:
程序通過連接器獲取了連接信息,要進行鏈接!!
進行鏈接就是去動態庫獲取寫入方法的地址;
優點:節省資源;
缺點:動態庫一旦丟失所有程序無法直接運行、速度慢;
靜態鏈接:? 把你要的方法直接拷貝到可執行程序中
動態庫把方法直接給了連接器,連接器進行鏈接時,直接給程序,和動態庫取消聯系,不依賴任何庫
優點:不依賴任何庫,自己獨立就能運行;
缺點:體積大,占據資源多(占據磁盤空間,內存空間)無法充分利用資源,加載速度受影響
我們的系統中一般默認沒有安裝c/c++的靜態庫,我們可以用命令行安裝c/c++的靜態庫:
使用yum(適用于CentOS,RHEL,Fedora)centos:
sudo yum install glibc-static
sudo yum install libstdc++-static
如果需要完整的的開發工具鏈(如gcc、g++等),可以安裝一下包:
sudo yum install gcc gcc-c++ make
使用apt(適用于Ubuntu,Debian等):
sudo apt install libc6-dev
sudo apt?install libstdc++-static-dev
如果需要完整的的開發工具鏈(如gcc、g++等),可以安裝一下包:
sudo apt install build-essential
3.4 靜態庫和動態庫
靜態庫是指編譯鏈接時,把庫?件的代碼全部加?到可執??件中,因此?成的?件?較?,但在運?時也就不再需要庫?件了。其后綴名?般為".a"
動態庫與之相反,在編譯鏈接時并沒有把庫?件的代碼加?到可執??件中,?是在程序執?時由運?時鏈接?件加載庫,這樣可以節省系統的開銷。動態庫?般后綴名為“.so”,如前?所述的libc.so.6 就是動態庫。gcc 在編譯時默認使?動態庫。完成了鏈接之后,gcc 就可以?成可執??件,如下所?。 gcc hello.o –o hello
gcc默認生成的?進制程序,是動態鏈接的,這點可以通過 file 命令驗證。
?
Linux下,動態庫XXX.so, 靜態庫XXX.a
Windows下,動態庫XXX.dll, 靜態庫XXX.lib
如何查看是否已經安裝epel-release源:
EPEL 倉庫的配置文件通常位于 /etc/yum.repos.d/
目錄下。你可以列出該目錄下的文件,看看是否有與 EPEL 相關的 repo 文件:
ls -l /etc/yum.repos.d/ | grep epel
也可以安裝一個廣泛使用的C++庫集合,提供了大量的功能,從智能指針到正則表達式支持等
# 安裝 Boost 靜態庫
sudo yum install boost-devel
二. 自動化構建 -make/Makefile
2.1 背景
會不會寫makefile,從?個側?說明了?個人是否具備完成?型工程的能力。
?個?程中的源?件不計數,其按類型、功能、模塊分別放在若?個?錄中,makefile定義了?系列的規則來指定,哪些?件需要先編譯,哪些?件需要后編譯,哪些?件需要重新編譯,甚?于進?更復雜的功能操作
makefile帶來的好處就是?“?動化編譯”,?旦寫好,只需要?個make命令,整個?程完全?動編譯,極?的提?了軟件開發的效率。
make是?個命令?具,是?個解釋makefile中指令的命令?具,?般來說,?多數的IDE都有這個命令,?如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可?,makefile都成為了?種在?程??的編譯?法。
make是?條命令,makefile是?個文件,兩個搭配使?,完成項目自動化構建
2.2 基本使用?
實例代碼
?
#include <stdio.h>
int main()
{
printf("hello Makefile!\n");
return 0;
}
Makefile文件
myproc:myproc.c
gcc -o myproc myproc.c
.PHONY:clean
clean:
rm -f myproc
依賴關系
上?的?件myproc,它依賴myproc.c
依賴方法
gcc -o myproc myproc.c ,就是與之對應的依賴關系
項?清理
?程是需要被清理的
像clean這種,沒有被第?個?標?件直接或間接關聯,那么它后?所定義的命令將不會被?動執?,不過,我們可以顯?要make執?。即命令?“make clean”,以此來清除所有的?標?件,以便重編譯。
但是?般我們這種clean的?標?件,我們將它設置為偽?標,? .PHONY 修飾,偽?標的特性是,總是被執?的。
這里就涉及到了總是被執行和不被執行兩個概念。
這里直接看圖詳解:
?
輸入make命令,make命令會自動在當前目錄下 找Makefile,然后用make去解釋Makefile里面的內容, 從上往下執行Makefile中的編譯方法,幫我們形成可執行程序。
有了.PHONY,make clean 就可以重復執行 rm -f code。? ? ?
.PHONY:讓make忽略源文件和可執行標文件的M時間對比
?
2.3 推導過程?
?
myproc:myproc.o
gcc myproc.o -o myprocmyproc.o:myproc.s
gcc -c myproc.s -o myproc.omyproc.s:myproc.i
gcc -S myproc.i -o myproc.smyproc.i:myproc.c
gcc -E myproc.c -o myproc.i.PHONY:clean
clean:
rm -f *.i *.s *.o myproc
編譯過程?
$ make
gcc -E myproc.c -o myproc.i
gcc -S myproc.i -o myproc.s
gcc -c myproc.s -o myproc.o
gcc myproc.o -o myproc
?
make是如何工作的,在默認的方式下,也就是我們只輸入make命令。那么:
- make會在當前?錄下找名字叫“Makefile”或“makefile”的?件。
- 如果找到,它會找?件中的第?個?標?件(target),在上?的例?中,他會找到 myproc 這個?件,并把這個?件作為最終的?標?件。
- 如果 myproc ?件不存在,或是 myproc 所依賴的后?的 myproc.o ?件的?件修改時間要? myproc 這個?件新(可以? touch 測試),那么,他就會執?后?所定義的命令來?成myproc 這個?件。
- 如果 myproc 所依賴的 myproc.o ?件不存在,那么 make 會在當前?件中找?標為myproc.o ?件的依賴性,如果找到則再根據那?個規則?成 myproc.o ?件。(這有點像?個堆棧的過程)
- ?當然,你的C?件和H?件是存在的啦,于是 make 會?成 myproc.o ?件,然后再? myproc.o ?件聲明 make 的終極任務,也就是執??件 hello 了。
- 這就是整個make的依賴性,make會?層??層地去找?件的依賴關系,直到最終編譯出第?個?標?件。
- ?在找尋的過程中,如果出現錯誤,?如最后被依賴的?件找不到,那么make就會直接退出,并報錯,?對于所定義的命令的錯誤,或是編譯不成功,make根本不理。
- make只管文件的依賴性,即,如果在我找了依賴關系之后,冒號后?的?件還是不存在,那么就停止工作
?
輸入make命令時,會讀取當前目錄下的Makefile文件,自頂向下對文件進行掃描,首先會發現code依賴的code.o不存在,因為code.o也有依賴關系,所以make進而找code.o的依賴關系,依次往下。到最后找到code.i依賴的code.c,依賴文件列表code.c本身在當前目錄下是已經存在的,所以code.c已經具備了能夠進行形成code.i的條件了,所以make就會執行code.i的依賴方法gcc -E code.c -o code.c這條命令,一旦code.i形成那么code.s,code.s所對應的依賴關系也有了,就會執行他的依賴方法。依次往上。---這就是make自動推導的過程。
當識別到code.o不存在會向下找,因為code.o也有依賴關系,所以make除了找code.o的依賴關系,還會把上一組code.o形成code的這組依賴方法入棧,然后依次往下,等到識別到code.c已經具備了能夠進行形成code.i的條件時,然后依次出棧執行依賴方法。
不想看這個命令執行的過程,只看結果,前面加@,關閉回顯
?
2.4 擴展語法
有了變量的定義,我們要定義的目標文件叫Bin,
源文件SRC
BIN=proc.exe # 定義變量
CC=gcc
#SRC=$(shell ls *.c) # 采?shell命令??式,獲取當前所有.c?件名
SRC=$(wildcard *.c) # 或者使? wildcard 函數,獲取當前所有.c?件名
OBJ=$(SRC:.c=.o) # 將SRC的所有同名.c 替換 成為.o 形成?標?件列表
LFLAGS=-o # 鏈接選項
FLAGS=-c # 編譯選項
RM=rm -f # 引?命令