這篇文章我們繼續(xù)學(xué)習(xí)Linux中的開發(fā)工具,今天要學(xué)的是:
Linux下的編譯器——gcc/g++
1. 概念
我們先來簡單了解一下它們的概念:
gcc (GNU Compiler Collection) 和 g++ 是 Linux 系統(tǒng)上最常用的編譯器。它們是 GNU 組織開發(fā)的一套開源編譯器工具集。
gcc:
gcc 是 GNU 編譯器集合中的 C 語言編譯器。
它支持多種 C 語言標(biāo)準(zhǔn)(如 ANSI C、ISO C89、ISO C99)以及一些擴(kuò)展特性。
gcc 可以將 C 語言源代碼編譯成可執(zhí)行文件,或者生成匯編代碼和目標(biāo)文件。
g++:
g++ 是 GNU 編譯器集合中的 C++ 語言編譯器。
它在 gcc 的基礎(chǔ)上添加了對 C++ 語言的支持,包括標(biāo)準(zhǔn) C++ 和一些擴(kuò)展特性。
g++ 可以將 C++ 源代碼編譯成可執(zhí)行文件,或者生成匯編代碼和目標(biāo)文件。
gcc 和 g++ 的選項(xiàng)基本上都是一樣的,我們這里就重點(diǎn)以gcc為例來進(jìn)行講解。
2. gcc 的使用
通過上面的了解我們知道gcc 和 g++ 其實(shí)就是Linux系統(tǒng)上的編譯器。
編譯器其實(shí)就是是一種將高級程序代碼(如 C、C++、Java 等)轉(zhuǎn)換為低級機(jī)器代碼或可執(zhí)行文件的軟件工具。
那在之前C語言的學(xué)習(xí)階段,我們其實(shí)有一篇文章比較詳細(xì)的講解了一下一個(gè)C程序從源文件變成可執(zhí)行程序的過程
大家如果不太清楚或者忘了的話可以復(fù)習(xí)一下——【C進(jìn)階】——我們寫的代碼是如何一步步變成可執(zhí)行程序(.EXE)的?
編譯器做的工作其實(shí)就是對應(yīng)圖中的翻譯環(huán)境中的幾步。
那至于詳細(xì)的過程,我們這里就不再展開討論了(大家可以看之前的文章),不過我們再來結(jié)合操作簡單的復(fù)習(xí)一下
之前的時(shí)候我們講解的并沒有讓大家關(guān)心具體的操作,那現(xiàn)在,大家就需要來學(xué)習(xí)一下操作過程中的這樣命令了。
我先來創(chuàng)建一個(gè)源文件myfile.c
對于這樣一個(gè)源文件,如果我們直接gcc編譯的話,他會(huì)直接做完整個(gè)翻譯過程,自動(dòng)生成一個(gè)名為a.out
的可執(zhí)行文件
我們就可以直接執(zhí)行輸出結(jié)果。
那其實(shí)這個(gè)可執(zhí)行文件的名字我們是可以自己指定的gcc -o 新生成文件名 原文件名
"-o"選項(xiàng)用于指定生成的可執(zhí)行文件或目標(biāo)文件的名稱。
-o之后一定是加我們自己給新生成文件起的名字
對于程序的翻譯,分為下面幾個(gè)過程:
2.1 預(yù)處理(預(yù)編譯)
預(yù)處理階段主要完成頭文件的展開、宏替換、條件編譯和去掉注釋等工作。
那上面我們演示的是從源文件直接生成可執(zhí)行文件,那如果我想讓它執(zhí)行完預(yù)處理過程就停下來呢?
這里用到另一個(gè)選項(xiàng):
-E
-E
:預(yù)處理之后就停下來
所以我執(zhí)行gcc -E myfile.c
但是這樣直接執(zhí)行之后它會(huì)把預(yù)處理之后文件的內(nèi)容直接顯示到顯示器上,這樣不太好看。
所以我們也可以-o指定一下文件名,把它放到對應(yīng)文件里
那對于預(yù)處理之后的文件,一般后綴為
.i
首先我們可以觀察到預(yù)處理之后文件大小大了很多。
然后我們可以用vim打開觀察一下,當(dāng)然打開myfile.c的同時(shí)我們可以借助vs 文件名
同時(shí)顯示myfile.i,分屏顯示,對比觀察一下
這里看到myfile.i的內(nèi)容是比較多的,我們看看他又多少行
一共有800多行,當(dāng)然這個(gè)其實(shí)我們C語言那篇文章也帶大家看過,其實(shí)前面多的這么多東西就是頭文件展開的內(nèi)容,當(dāng)然還會(huì)做一些其它動(dòng)作比如注釋的刪除、宏替換和條件編譯語句的處理等。
不過這里我們沒用這么多東西。
2.2 編譯
編譯過程,就是把預(yù)處理之后的C語言代碼轉(zhuǎn)換成匯編代碼
那如果我想讓編譯結(jié)束停下來呢?
-S
:編譯結(jié)束就停止
那這里我們看到不指定名字的話,他自動(dòng)把生成的文件命名為.s
后綴的(編譯之后文件后綴為.s)
當(dāng)然我們還可以自己指定
那我們打開看一下
那這里里面放的其實(shí)就是對應(yīng)的匯編代碼
2.3 匯編
匯編其實(shí)就是把匯編指令轉(zhuǎn)化為二進(jìn)制的機(jī)器指令,生成對應(yīng)的可重定位的二進(jìn)制目標(biāo)文件。
那我想查看匯編之后的文件:
-c
:匯編結(jié)束就停止
當(dāng)然這個(gè)我們是看不懂的,它是一種二進(jìn)制文件
2.4 鏈接
鏈接過程是將多個(gè)目標(biāo)文件(可重定位目標(biāo)文件)以及庫文件組合在一起,生成最終的可執(zhí)行文件。
那要鏈接生成可執(zhí)行文件的話其實(shí)就不用選項(xiàng)了:
直接對匯編生成的.o文件進(jìn)行g(shù)cc就可以生成最終的可執(zhí)行程序
就可以執(zhí)行了
把這整個(gè)過程我們又過了一遍。
但是,下面關(guān)于鏈接過程中的某些內(nèi)容,我們還要來探討一下
3. 動(dòng)態(tài)庫和靜態(tài)庫
上面說到在鏈接過程中我們的程序會(huì)和用到的一些庫鏈接到一起。
大家思考一個(gè)問題,為什么我們在Linux上可以進(jìn)行C/C++代碼的編譯鏈接這些動(dòng)作呢?
其實(shí)其中一個(gè)比較重要的原因就是Linux提供了這些語言所需要的開發(fā)庫,如標(biāo)準(zhǔn)C庫(libc)、標(biāo)準(zhǔn)C++庫(libstdc++)以及其他各種系統(tǒng)庫和第三方庫。這些庫提供了大量的函數(shù)和工具,方便開發(fā)者編寫各種類型的應(yīng)用程序。
那其實(shí)我們可以看一下我們當(dāng)前的Linux系統(tǒng)上都提供了那些庫:ls /usr/include
在這個(gè)路徑下
我們看到里面有些頭文件其實(shí)是我們比較熟悉的。
那下面我們就來學(xué)習(xí)這樣一條命令:
ldd
:ldd命令用于打印一個(gè)可執(zhí)行文件或共享庫文件依賴的動(dòng)態(tài)鏈接庫(shared library)列表。它會(huì)遞歸地檢查可執(zhí)行文件或共享庫文件所依賴的其他庫文件,以及這些依賴的庫文件的依賴,一直到所有依賴的庫文件列表打印完畢。
比如,對于我們上面生成的可執(zhí)行程序,我們就可以使用ldd命令查看一下它都依賴了哪些庫
我們看到這里打印出來有3條,不過我們重點(diǎn)關(guān)心一下第二個(gè)。
第二個(gè)libc.so.6
其實(shí)就是Linux中的C標(biāo)準(zhǔn)庫。
另外我們在安裝一些ide的時(shí)候,比如就拿我用的這個(gè)vs2022來說,我們安裝它的時(shí)候,其實(shí)一個(gè)比較重要的工作就是安裝相關(guān)的庫之類的東西。
就類似這個(gè)圖。
在這里涉及到一個(gè)重要的概念——庫(函數(shù)庫)
我們的C程序中,并沒有定義“printf”的函數(shù)實(shí)現(xiàn),且在預(yù)編譯中包含的“stdio.h”中也只有該函數(shù)的聲明,而沒有定義函數(shù)的實(shí)現(xiàn),那么,是在哪里實(shí)“printf”函數(shù)的呢?
最后的答案是:
系統(tǒng)把這些函數(shù)實(shí)現(xiàn)都被做到名為 libc.so.6 的庫文件中去了,在沒有特別指定時(shí),gcc 會(huì)到系統(tǒng)默認(rèn)的搜索路徑“/usr/lib”下進(jìn)行查找,也就是鏈接到 libc.so.6 庫函數(shù)中去,這樣就能使用函數(shù)“printf”了,而這也就是鏈接的作用
那說到庫,其實(shí)分為兩種(庫本質(zhì)也是文件):
- 動(dòng)態(tài)庫
在Linux中,動(dòng)態(tài)庫一般是以
lib
開頭,.so
為后綴
即libXXXXX.so
- 靜態(tài)庫
靜態(tài)庫一般以
lib
開頭,.a
為后綴
即libXXXXX.a
3.1 動(dòng)靜態(tài)庫的理解
那這里提到的動(dòng)態(tài)庫和靜態(tài)庫該怎么理解呢?
當(dāng)我們談?wù)搸欤↙ibrary)時(shí),可以將其比作圖書館,而靜態(tài)庫和動(dòng)態(tài)庫則是兩種不同的圖書存放方式。
想象一下,你是一名學(xué)生,圖書館中有很多有用的書籍,供你在學(xué)習(xí)過程中參考使用。
靜態(tài)庫(Static Library) 類似于你個(gè)人的書包,你從圖書館中選擇了一些書籍,把它們拷貝到你的書包里。這些書籍是你個(gè)人擁有的,可以在需要的時(shí)候直接使用。當(dāng)你需要使用這些書籍時(shí),你只需從書包中取出,不需要依賴圖書館,也不會(huì)影響其他學(xué)生。
在編程中,靜態(tài)庫是在編譯時(shí)將庫的代碼和程序代碼鏈接在一起,形成一個(gè)單獨(dú)的可執(zhí)行文件。這意味著靜態(tài)庫的代碼被復(fù)制到了最終的可執(zhí)行文件中(這種鏈接方式我們稱為靜態(tài)鏈接),程序在運(yùn)行時(shí)不需要外部的庫文件依賴。這樣做的好處是,程序更加獨(dú)立,可以在不同的系統(tǒng)中運(yùn)行,不受外部環(huán)境的影響。
但是生成的文件比較大。
動(dòng)態(tài)庫(Dynamic Library) 類似于圖書館中的共享書架,每個(gè)學(xué)生都可以訪問這些書架上的書籍。當(dāng)你需要使用這些書籍時(shí),你可以從書架上取出,使用完畢后放回書架上供其他人使用。這意味著多個(gè)程序可以共享同一個(gè)動(dòng)態(tài)庫,減少了存儲(chǔ)空間的占用。
在編程中,動(dòng)態(tài)庫是在運(yùn)行時(shí)由操作系統(tǒng)加載的庫文件,程序在運(yùn)行時(shí)需要由鏈接器引入動(dòng)態(tài)庫,才能使用其中的函數(shù)或資源。可執(zhí)行文件中只包含對庫函數(shù)的引用或者說地址,而不復(fù)制庫的代碼和數(shù)據(jù)(動(dòng)態(tài)鏈接)。這樣做的好處是,多個(gè)程序可以共享同一個(gè)動(dòng)態(tài)庫,減少了內(nèi)存的占用和可執(zhí)行文件的大小。
總結(jié)起來:
靜態(tài)庫將庫的代碼復(fù)制到可執(zhí)行文件中,使得程序獨(dú)立運(yùn)行;動(dòng)態(tài)庫則共享在操作系統(tǒng)中,減少了內(nèi)存占用和可執(zhí)行文件的大小。靜態(tài)庫適合小型獨(dú)立程序,而動(dòng)態(tài)庫適合大型程序或多個(gè)程序共享使用。
3.2 默認(rèn)是動(dòng)態(tài)鏈接,我們?nèi)绾芜M(jìn)行靜態(tài)
然后想告訴大家,在我們的Linux上,默認(rèn)只有動(dòng)態(tài)庫,進(jìn)行的是動(dòng)態(tài)鏈接
之前我們學(xué)過一個(gè)file指令,它可以更清晰的顯示一個(gè)文件的類型
我們執(zhí)行file myfile.exe
就可以看到里面有一個(gè)dynamically linked
,它的意思就是動(dòng)態(tài)鏈接
那如果我們想進(jìn)行靜態(tài)鏈接,能做到嗎?
可以的。
不過呢,一般我們的Linux上默認(rèn)只有動(dòng)態(tài)庫,所以,如果想進(jìn)行靜態(tài)鏈接的話,需要先安裝一下靜態(tài)庫:yum install -y glibc-static libstdc++-static
把指令給大家,大家直接執(zhí)行就行(普通用戶+sudo),這條指令是把C和C++的靜態(tài)庫都安裝上的。
然后就可以進(jìn)行靜態(tài)鏈接了
對應(yīng)的命令是
gcc -static
:靜態(tài)鏈接
??,大家看到了嗎,靜態(tài)鏈接生成的可執(zhí)行文件比動(dòng)態(tài)鏈接生成的大了好多好多。
我們也可以用file查看一下靜態(tài)鏈接生成的可執(zhí)行文件
里面也能看到靜態(tài)鏈接statically linked
文章來源:http://www.zghlxwxcb.cn/news/detail-576793.html
關(guān)于動(dòng)靜態(tài)庫我們先了解到這里,后續(xù)還會(huì)進(jìn)行更深入的學(xué)習(xí)…文章來源地址http://www.zghlxwxcb.cn/news/detail-576793.html
到了這里,關(guān)于【Linux系統(tǒng)編程】Linux下的編譯器——gcc/g++的使用 及 動(dòng)態(tài)庫和靜態(tài)庫的認(rèn)識的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!