堅持用 清晰易懂的圖解 + 代碼語言,讓每個知識點變得簡單!
🚀呆頭個人主頁詳情
🌱 呆頭個人Gitee代碼倉庫
📌 呆頭詳細專欄系列
座右銘: “不患無位,患所以立。”
《Linux編譯器:GCC/G++食用指南》
- 前言
- 目錄
- 一、gcc/g++的簡單使用
- 1.作用
- 2.語法
- GCC/G++ 編譯選項速查表
- 二、gcc/g++編譯器的執行步驟
- 1.預處理
- 2.編譯
- 3.匯編
- 4.鏈接
- * 程序執行 *
- 三、庫
- 1.庫的概念
- 2.庫的分類
- 3 .o與庫的鏈接
- 4.優缺點
- **動態庫 vs 靜態庫終極對比表**
前言
🚀 歡迎來到《Linux系統實戰》!
這里是命令行到內核的躍遷基地,也是你從"rm -rf恐懼癥"到"權限管理大師"的修煉場。
🔍 專欄特色:
- 圖解+實戰:用最直觀的方式拆解Linux核心機制
- 從應用到底層:覆蓋Shell腳本、系統調優、內核模塊開發
- 真實場景:每篇附服務器運維/開發中的實際問題解決方案
💡 學習建議:
1?? 先動手嘗試(搞崩了也沒關系)
2?? 對照文章分析原理
3?? 用文末【實戰任務】鞏固技能
📌 Linux經典名言:
“Linux不是背出來的,是在一次次Permission denied
中練出來的!”
(正文開始👇)
目錄
一、gcc/g++的簡單使用
1.作用
gcc和g++分別是GNU的C和C++的編譯器,gcc和g++在執行編譯的時候一般有以下四個步驟:
1)預處理(頭文件展開、去注釋、宏替換、條件編譯)。
2)編譯(C代碼翻譯成匯編語言)。
3)匯編(匯編代碼轉為二進制目標代碼)。
4)鏈接(將匯編過程產生的二進制代碼進行鏈接)。
2.語法
GCC/G++ 編譯選項速查表
選項 | 功能說明 | 備注 |
---|---|---|
-E | 只進行預處理,不生成文件 | 需手動重定向到輸出文件(如 gcc -E test.c > test.i ) |
-S | 編譯到匯編語言(生成 .s 文件),不進行匯編和鏈接 | 保留預處理 + 編譯結果 |
-c | 編譯到目標代碼(生成 .o 文件),不鏈接 | 適用于分步編譯 |
-o <file> | 指定輸出文件名 | 如 gcc test.c -o test |
-static | 強制靜態鏈接(生成文件較大) | 優先鏈接靜態庫而非動態庫 |
-g | 生成調試信息(GDB 可用) | 默認生成 release 版本需顯式添加此選項 |
-shared | 生成動態鏈接庫(.so 文件) | 通常配合 -fPIC 使用 |
-w | 禁用所有警告信息 | 不推薦使用(可能掩蓋潛在問題) |
-Wall | 開啟所有標準警告信息 | 實際不包括所有警告(建議結合 -Wextra ) |
-O0 | 不優化(調試時推薦) | 保留原始代碼結構 |
-O1 | 基礎優化(默認級別) | 在編譯速度和性能間平衡 |
-O2 | 深度優化(推薦發布使用) | 包含大多數安全優化選項 |
-O3 | 激進優化(可能增加代碼體積) | 可能改變程序行為(需嚴格測試) |
小技巧:
- 組合使用示例:
g++ -Wall -O2 -g main.cpp -o app
- 查看完整選項:
gcc --help
或man gcc
二、gcc/g++編譯器的執行步驟
gcc和g++的執行步驟中的指令都是相同的,這里小編以linux中的gcc編譯器的執行步驟中的指令為例進行講解
1.預處理
預處理功能主要包括宏定義,文件包含,條件編譯,去注釋等。
- 預處理指令是以#號開頭的代碼行。
- 實例: gcc –E hello.c –o hello.i
- 選項“-E”,該選項的作用是讓 gcc 在預處理結束后停止編譯過程。
- 選項“-o”是指目標文件,“.i”文件為已經過預處理的C原始程序。
- 去掉注釋
注釋是寫給開發者人員的自然對于程序的編譯自然無用,所以編譯器將對于編譯階段無用的注釋去掉- 頭文件的展開
頭文件既然可以進行展開,那么說明在系統路徑下一定有地方存放頭文件,在預處理階段將頭文件的內容拷貝到源文件中- 條件編譯
可以支持對代碼的裁剪工作,例如應用的社區版和免費版其實就是應用了條件編譯,使同一份代碼經過條件編譯后呈現出兩種不同的狀態- 宏替換
特別講解一下這里的宏替換不進行語法檢查,我們知道在編譯階段進行語法的檢查,而宏替換是在編譯階段之前,即預處理階段已經完成了宏替換,即預處理完之后由于宏已經被替換了,所以此時宏就沒有了,自然在進行編譯階段不進行宏替換的語法檢查。
2.編譯
gcc -S code.i -o code.s
gcc -S 要進行編譯的文件名(建議使用預處理后以 .i 為后綴的文件) -o 編譯后的文件名(這里建議使用 .s 為后綴),即告訴編譯器從現在開始進行翻譯,當編譯工作完成后,就停下來
- 在這個階段中,gcc/g++首先檢查代碼的規范性、是否有語法錯誤等,以確定代碼的實際要做的工作,在檢查無誤后,將代碼翻譯成匯編語言。
- 用戶可以使用-S選項來進行查看,該選項只進行編譯而不進行匯編,生成匯編代碼。
- -o選項是指目標文件,“xxx.s”文件為已經過翻譯的原始程序。
3.匯編
gcc -c code.s -o code.0
gcc -c 要進行匯編的文件名(這里建議使用編譯后的.s文件) -o 匯編后的文件名(這里建議使用.o為后綴的文件名),告訴編譯器開始進行程序的翻譯,當匯編工作完成后,就停下來
- 匯編階段是把編譯階段生成的“xxx.s”文件轉成目標文件。
- 使用-c選項就可以得到匯編代碼轉化為“xxx.o”的二進制目標代碼了。
4.鏈接
gcc code.o -o code
gcc 要進行鏈接的文件(這里建議使用執行完匯編后生成的.o文件) -o 鏈接后的文件名,將編譯生成的可重定位目標二進制文件(目標文件)和庫進行鏈接生成可執行程序
- 在成功完成以上步驟之后,就進入了鏈接階段。
- 鏈接的主要任務就是將生成的各個“xxx.o”文件進行鏈接,生成可執行文件。
- gcc/g++不帶-E、-S、-c選項時,就默認生成預處理、編譯、匯編、鏈接全過程后的文件。
- 若不用-o選項指定生成文件的文件名,則默認生成的可執行文件名為a.out。
注意: 鏈接后生成的也是二進制文件。
* 程序執行 *
./可執行文件名,即可執行可執行文件
三、庫
1.庫的概念
- 在鏈接的時候會進行匯編生成的二進制文件和庫進行鏈接,那么這個庫具體是指的是什么呢?
- 拿c語言程序中的printf函數來說,在程序中調用printf打印hello,#include <stdio.h>頭文件中是printf函數的聲明,那么實現究竟是在哪里呢?沒錯,在庫中,
- 在庫中提供函數方法的實現,在c語言中即為c語言標準庫
庫的本質是文件,有其對應的路徑在linxu中為(動態庫)/usr/lib64/libc.so或(靜態庫)/usr/lib64/libc.a
2.庫的分類
- linux:.so(動態庫) .a(靜態庫)
- windows:.dll(動態庫) .lib(靜態庫)
- 系統默認只提供動態庫,為/usr/lib64/libc.so,但是對于靜態庫/usr/lib64/libc.a系統默認不提供即沒有進行安裝,這時候我們就要自行去安裝靜態庫或動態庫
動態庫
# 安裝運行時庫(僅動態庫,無頭文件)
sudo yum install curl# 安裝開發包(包含頭文件和動態庫鏈接)
sudo yum install libcurl-devel
靜態庫
yum install glibc-static libstdc++-static -y
方法的實現其實就在庫中
庫其實是將源文件經過翻譯,打包成為一個文件(不用提供太多的源文件,也達到了隱藏源文件的目的),庫避免了一些經常性使用的函數實現的重復書寫,節省工作,提高效率
你的軟件=頭文件提供聲明+庫提供函數的實現+你的代碼
3 .o與庫的鏈接
庫分為動態庫(共享庫)和靜態庫,那么對應有動態鏈接和靜態鏈接
動態鏈接:
- 動態鏈接是在程序執行時由運行時的鏈接文件加載庫,所以很多程序的運行都要依賴動態庫的存在,所以動態庫不能缺失,一旦缺失會造成很多程序無法運行,進而可能造成無法使用操作系統
- 靜態鏈接:
編譯器在使用靜態庫進行靜態鏈接的時候,會將靜態庫的方法拷貝到目標程序中,需要使用哪些函數方法就將哪些函數方法拷貝到目標文件中,并不是將靜態庫的所有方法都拷貝到目標文件中,這一點經常被混淆,請讀者友友們注意區分,此后程序的執行不再需要依賴靜態庫在linux中,編譯形成可執行程序,默認采用的鏈接方式是動態鏈接—動態庫
ldd 可執行程序 可以查看或打印一個程序運行所需的共享庫
觀察一下靜態鏈接方式由于是將庫進行拷貝,所以占用空間明顯大于動態鏈接(程序執行時由運行的鏈接文件進行加載庫)方式
- 如果沒有靜態庫,我們不能使用-static進行靜態鏈接
- 如果沒有動態庫,只有靜態庫,并且編譯器gcc可以找到靜態庫,此時編譯器采用靜態鏈接將我們的目標文件和庫進行鏈接,因為當系統中存在動態庫的時候,系統會優先采用動態鏈接,當不存在動態庫的時候,編譯器會再去看看系統中有沒有靜態庫,如果有靜態庫則采用靜態鏈接的方式,如果連靜態庫都沒有則報錯
- 同時在我們的可執行文件中,并不是所有的鏈接方式都是純動態鏈接的,而是動態鏈接和靜態鏈接混合的方式
- file 可執行程序,可以查看我們的可執行程序的鏈接方式
4.優缺點
以下是符合 CSDN 博客風格的 Markdown 表格,對比動態庫和靜態庫的優缺點,并優化了可讀性和技術專業性:
動態庫 vs 靜態庫終極對比表
特性 | 動態庫(.so/.dll) | 靜態庫(.a/.lib) |
---|---|---|
🔧 編譯方式 | 編譯時記錄依賴,運行時加載 | 編譯時直接嵌入到可執行文件中 |
📦 文件體積 | ? 極小(僅記錄符號) ? 多個程序共享同一份庫文件 | ? 巨大(庫代碼直接復制到程序) ? 每個程序都包含完整庫代碼 |
🏃 運行依賴 | ? 必須存在 ? 缺失時報錯: error while loading shared libraries | ? 完全獨立 ? 單文件即可運行 |
🔄 更新維護 | ? 熱更新 ? 替換 .so 文件立即生效 | ? 需重新編譯 ? 修復庫=重新發布所有程序 |
? 運行性能 | ?? 略慢(首次加載需解析符號) | ? 極致快(無運行時鏈接開銷) |
💣 兼容性 | ? 地獄級難題 ? glibc 版本沖突常見 | ? 無煩惱 ? 庫和程序成綁定關系 |
🛡? 安全性 | ?? 有風險 ? 可能被惡意替換成釣魚庫 | ? 鐵布衫 ? 代碼全內嵌防篡改 |
🚀 適用場景 | ? 桌面應用(如WPS) ? 微服務容器 ? 插件系統(如Nginx模塊) | ? 嵌入式設備 ? 路由器固件 ? 安全敏感工具(如 tcpdump ) |
💻 部署難度 | ? 需處理依賴樹 ? LD_LIBRARY_PATH /rpath 配置復雜 | ? 雙擊即用 ? 無環境依賴 |