|
前言:gcc/g++/gdb,程序員的“魔法棒”大家好,我是小康。今天我們來聊下怎樣來編譯和調試 C/C++ 程序。
提到 gcc/g++,很多初學者的第一反應可能是:
“這不是編譯器嘛,不就寫 gcc main.c 然后敲個回車?”
但當編譯報錯時,才發(fā)現(xiàn)自己對它的了解就像對前任一樣——一知半解。
其實,gcc/g++ 不只是一個“會把 C/C++ 代碼變成可執(zhí)行文件”的工具,它還能優(yōu)化、調試、排錯,甚至分析代碼!今天,小康就來帶你解鎖 gcc/g++/gdb 的正確姿勢:從編譯到調試,通通搞明白!
小貼士:
gcc:C 編譯器,專門編譯 C 程序。g++ : C++編譯器,專門編譯 C++ 程序。
gcc 和 g++的用法和參數(shù)基本相同,今天我們主要介紹 gcc!gdb :調試 C/C++ 程序的利器!1、什么是 gcc?簡單聊聊它的身份gcc,全稱 GNU Compiler Collection,是一款強大的開源編譯器,支持多種語言(C、C++、Objective-C 等)。但今天,我們只專注它在 C/C++ 編譯領域的表現(xiàn)。
一句話概括 gcc 的工作:
把你寫的代碼從“人話”翻譯成機器能看懂的“機器語言”。即:將你的程序代碼編譯成計算機能夠識別的機器語言(01機器碼)。
gcc 的核心流程分為四步:預處理:處理宏定義、頭文件、條件編譯等。編譯:將預處理的代碼轉成匯編代碼。匯編:把匯編代碼轉成機器代碼(生成目標文件)。鏈接:將目標文件生成可執(zhí)行文件。[/ol]2、GCC 的安裝與檢查2.1 檢查是否已安裝在終端輸入以下命令:
gcc --version
如果返回版本信息,說明 GCC 已經(jīng)安裝成功。如果提示 command not found,那就繼續(xù)看下面的安裝步驟。
2.2 安裝 GCC/G++/GDBUbuntu/Debian 系統(tǒng):[/ol]sudo apt update
sudo apt install build-essential -y # 安裝 gcc 和 g++
sudo apt install gdb # 安裝 gdb
這會同時安裝 GCC 和其他編譯工具鏈。CentOS/Red Hat 系統(tǒng):[/ol]sudo yum groupinstall "Development Tools" -y # 安裝 gcc 和 g++
sudo yum install gdb -y # 安裝 gdb驗證安裝:分別運行 gcc --version 、 g++ --version和 gdb --version,確認 GCC/G++/GDB 是否安裝成功。[/ol]3. gcc 的基本用法:從入門到熟練3.1 最簡單的編譯指令gcc main.c -o main
main.c 是你的代碼文件。-o main 指定生成的可執(zhí)行文件名為 main。如果不寫 -o,默認生成名為 a.out 的文件。運行程序:
./main
就這么簡單,一行命令搞定編譯和運行,是不是挺方便?但這其實是“打包式”的操作,編譯和鏈接一起完成。如果你是剛入門的初學者,可能還不知道 GCC 背后做了些什么。這時,我們可以試試 分步編譯,讓每一步變得更清晰。
3.2 分步編譯指令分步編譯可以幫你更好地理解編譯器的工作流程。其實,GCC 編譯分為兩個主要階段(G++ 類似):編譯階段:將源碼翻譯成機器能理解的中間文件(目標文件,.o 文件)。鏈接階段:將目標文件鏈接成最終的可執(zhí)行文件。[/ol]第一步:編譯源程序文件
運行以下命令,將 main.c 轉換成目標文件 main.o:
gcc -c main.c -o main.o
-c 參數(shù)表示只編譯,不鏈接。main.o 是生成的目標文件,雖然不能直接運行,但它已經(jīng)包含了 main.c 的翻譯結果。第二步:鏈接目標文件
接下來,將目標文件 main.o 鏈接成可執(zhí)行文件 main:
gcc main.o -o main
這一步不再使用 -c,而是使用 -o,因為我們要讓 GCC 把目標文件鏈接成一個完整的程序。運行程序:
./main
同樣的輸出,經(jīng)過分步操作生成了結果,是不是感覺自己更專業(yè)了?
為什么要分步編譯?
你可能會想:“分兩步多麻煩啊,我直接一步編譯不就行了?”其實,分步編譯有它的優(yōu)勢:
更高的靈活性:當項目中有多個文件時,你只需要重新編譯修改過的文件,其他部分可以直接復用之前生成的目標文件(.o文件 ),大大提高效率。(下文會提到多個文件編譯的情況)清晰的流程:每一步的工作職責都很明確,便于排查問題。例如,如果某個文件編譯不過,可以單獨解決,而不用從頭來過。3.2 常用選項大盤點1. 加點料,讓錯誤信息更清晰:
gcc -Wall -Wextra main.c -o main
-Wall:開啟常見警告(比如變量聲明但沒使用 -Wunused-variable )-Wextra:開啟額外警告(如未使用函數(shù)參數(shù) -Wunused-parameter)2. 為調試準備,加上調試符號:
gcc -g main.c -o main
-g:生成調試信息,方便用 GDB 調試。3. 編譯多個文件:
gcc file1.c file2.c -o program
多個源文件會一起編譯鏈接成一個可執(zhí)行文件。4. 優(yōu)化代碼(-O 系列)
讓程序更快?試試優(yōu)化選項:
-O0:不優(yōu)化(默認)
gcc 默認不會優(yōu)化代碼,生成的程序跟你寫的源代碼更接近。
啥時候用?
開發(fā)調試時,容易追蹤代碼邏輯。編譯示例:
# 這兩個命令效果一樣
gcc hello.c -O0 -o hello
gcc hello.c -o hello
-O1:基礎優(yōu)化
會優(yōu)化掉無用代碼,讓程序稍微跑快一點,但調試依然友好。
啥時候用?
需要一點性能提升,但還得經(jīng)常調試代碼的時候。編譯示例:
gcc hello.c -O1 -o hello
-O2:常用優(yōu)化(推薦!)
在 -O1 的基礎上,增加更多優(yōu)化,比如減少循環(huán)次數(shù)、改進分支預測等。
啥時候用?
程序跑得還可以,但希望它跑得更穩(wěn)更快。適合大部分場景。編譯示例:
gcc hello.c -O2 -o hello
-O3:更高級優(yōu)化
比 -O2 更激進,開啟一些高級優(yōu)化,比如函數(shù)內(nèi)聯(lián)和向量化。
啥時候用?
追求極限性能的程序,比如科學計算、大型數(shù)據(jù)處理。但注意,有時候優(yōu)化過頭會導致兼容性問題(比如浮點運算不準)。編譯示例:
gcc hello.c -O3 -o hello
總結:選擇適合的優(yōu)化級別開發(fā)階段:-O0 或 -O1,方便調試。生產(chǎn)環(huán)境:-O2 是最平衡的選項,跑得快又穩(wěn)。極限性能:-O3 ,但要注意兼容性和精度問題。根據(jù)場景選個合適的優(yōu)化級別,你的代碼就能跑得既穩(wěn)又快!
4、多文件項目的編譯在實際項目中,代碼往往分成多個文件,比如:
main.cutils.cutils.h方法一:一次性編譯gcc main.c utils.c -o my_program
優(yōu)勢:
簡單粗暴:一條命令搞定所有文件,適合小項目。快速省事:文件少的時候,用起來方便快捷。方法二:分步編譯再鏈接gcc -c main.c -o main.o
gcc -c utils.c -o utils.o
gcc main.o utils.o -o my_program
優(yōu)勢:
效率高:只編譯修改過的文件,不用每次全編譯。更靈活:大項目用分步編譯更好管理,還能配合自動化工具用。建議:
文件少就用方法一,文件多或者想提高效率就用方法二,兩個都得會,隨場景切換!
5. gcc 編譯流程深入解析:搞懂每一步如果只知道用 gcc 編譯,算是入門;但要真正搞懂 gcc,必須了解它的四步工作流程。
5.1 預處理:先搞定頭文件和宏gcc -E main.c -o main.i
-E:只執(zhí)行預處理,輸出結果是 main.i。預處理會替換 #include 的頭文件內(nèi)容,展開宏定義,去掉注釋。打開生成的文件,你能看到“裸露”的預處理后代碼。5.2 編譯:從人話到匯編gcc -S main.i -o main.s
-S:將預處理后的代碼轉成匯編代碼,結果是 main.s。匯編代碼是介于高級語言和機器語言之間的一種語言,更接近機器。5.3 匯編:把匯編轉成機器碼gcc -c main.s -o main.o
-c:只執(zhí)行編譯到匯編的這一步,生成目標文件(main.o)。目標文件是二進制的,但還不能直接運行。5.4 鏈接:生成可執(zhí)行文件gcc main.o -o main
鏈接器負責將目標文件和系統(tǒng)庫一起鏈接,生成最終的可執(zhí)行文件。各文件內(nèi)容對比:
文件名內(nèi)容類型用 cat
/vim
查看結果更合適的查看方式main.i預處理后的源碼源碼,可讀無需額外工具,直接查看main.s匯編代碼匯編指令,可讀無需額外工具,直接查看main.o二進制目標文件亂碼,不可讀objdump
或 readelfmain可執(zhí)行文件,機器碼亂碼,不可讀objdump
或 readelfPS: gcc 和 g++ 的用法及參數(shù)基本相同。要編譯 C++ 程序,只需把命令中的 gcc 換成 g++,比如編譯 main.cpp:g++ main.cpp -o main。C++ 程序的文件通常以 .cpp 為后綴,如 main.cpp。
6. 調試利器:GDB 上線了寫代碼,最怕的是:程序掛了,但根本不知道為什么掛。
這時,調試工具 GDB 就派上用場了。
6.1 用 gcc 編譯時加調試信息gcc -g main.c -o main
-g 選項主要是生成調試信息,方便用 GDB 調試。6.2 常用 GDB 命令1. 啟動 GDB:
gdb ./main
進入 GDB 調試模式。2. 設置斷點:
break
比如 break 10,在代碼第 10 行 設置斷點 。3. 運行程序:
run
運行程序,停在斷點處。
4. 單步執(zhí)行:
單步執(zhí)行,不進入函數(shù)next
單步執(zhí)行,進入函數(shù)。step
選擇 next 跳過函數(shù),step 進入函數(shù),按需使用即可!
5. 查看變量值:
print
比如 print x,顯示當前變量 x 的值。6. 查看當前代碼(上下文代碼)
查看當前執(zhí)行位置的代碼:list
默認顯示當前斷點附近的代碼。
指定顯示某行附近的代碼:list
比如 list 20,顯示第 20 行附近的代碼。
7. 打印函數(shù)調用棧
查看調用棧信息:backtrace
顯示當前函數(shù)被哪個函數(shù)調用,調用者又是誰,一層層往上追溯。對于分析崩潰點(coredump)特別有用。8. 查看所有斷點
列出當前所有斷點:info breakpoints
可以看到每個斷點的編號、位置等信息。
9. 刪除斷點
刪除某個斷點:delete
比如 delete 1,刪除編號為 1 的斷點。
刪除所有斷點:delete
10. 繼續(xù)運行程序
從當前斷點繼續(xù)運行程序:continue
程序會從當前斷點繼續(xù)跑,直到遇到下一個斷點或結束。11. 退出調試:
quit
7.常見問題排查7.1 缺少頭文件報錯:stdio.h: No such file or directory
原因:簡單說,編譯器找不到 stdio.h 這個標準頭文件,可能是系統(tǒng)里沒裝編譯工具包,缺了開發(fā)相關的庫。
解決方法:
在 Ubuntu/Debian 系統(tǒng)上,安裝必備工具包:使用命令 sudo apt install build-essential 這會把 gcc、g++ 和相關頭文件都裝上。在 CentOS/Red Hat 系統(tǒng)上,安裝開發(fā)工具:sudo yum groupinstall "Development Tools"
這樣能確保編譯環(huán)境完整無缺。
7.2 “段錯誤”報錯:Segmentation fault (core dumped)
原因:簡單說,程序想訪問一塊不該碰的內(nèi)存,比如:
用了“野指針”(指針沒初始化,隨便指向了某個未知地址)。數(shù)組越界了,訪問了數(shù)組的第“10086”個元素,而數(shù)組長度只有 100 個。使用了已經(jīng)釋放的內(nèi)存。解決方法:
1、檢查指針和數(shù)組:
確保指針初始化,比如:int *ptr ;
*ptr = 10; // 未初始化就賦值,肯定會報錯:Segmentation fault (core dumped)
不要訪問超出數(shù)組范圍的元素,比如訪問 arr[10086],而數(shù)組只有 100 個元素。如果是動態(tài)內(nèi)存分配(malloc/free),檢查是否釋放了兩次。2、用 GDB 調試:
編譯時加上 -g 參數(shù)生成調試信息:gcc -g main.c -o main
然后用 GDB 跑程序:gdb ./main
run
程序崩潰時,輸入:bt
GDB 會告訴你出錯在哪一行。
調試時別慌,找到那行代碼,慢慢改,段錯誤就能搞定!
7.3 鏈接錯誤報錯:undefined reference to '某函數(shù)'
原因:這句話的意思很簡單,你的代碼用到了一個函數(shù),但是編譯器在鏈接階段找不到它的實現(xiàn)?赡苁牵
忘了加實現(xiàn)的文件:函數(shù)寫在另一個 .c 文件里,編譯時沒有包含進去。漏了庫的鏈接:用到了外部庫的函數(shù),但沒告訴編譯器要用哪個庫。函數(shù)聲明沒問題,函數(shù)實現(xiàn)卻沒寫,編譯器不知道該去哪里找它。解決方法:
如果函數(shù)是你自己寫的,確保編譯時包含了所有相關文件:gcc main.c func.c -o main
沒加就補上!
如果是庫函數(shù),比如用到了數(shù)學庫的 sqrt,需要鏈接對應的庫,加上 -lm:gcc main.c -lm -o main
這里的 -lm 表示鏈接數(shù)學庫(math library)。
檢查函數(shù)實現(xiàn)是否真的寫了!如果只是聲明了函數(shù):void my_function();
但實現(xiàn)忘了寫,肯定會報錯。趕緊補上實現(xiàn)!
這個鏈接錯誤其實很常見,仔細檢查文件和庫就能解決!
7.4 未定義的函數(shù)引用報錯:undefined reference to '某函數(shù)'原因:簡單說,這個報錯就是你的代碼用到了某個函數(shù),但編譯器找不到它的實現(xiàn)。可能是你沒把實現(xiàn)的文件加到編譯命令里,或者用到了外部庫卻忘了鏈接。解決方法:1、如果是你自己寫的函數(shù),確認它的實現(xiàn)文件是否加到編譯命令里,比如:gcc main.c func.c -o main
沒加就補上!
2、如果是用的庫函數(shù),比如數(shù)學庫里的 sqrt,就加上對應的庫鏈接,比如:
gcc main.c -lm -o main
這個 -lm 是告訴編譯器“我要用數(shù)學庫”。
7.5 GDB 提示沒有調試信息報錯:No debugging symbols found原因:這個報錯意思很簡單:你的程序沒帶“調試信息”。GDB 說,“你讓我調試代碼,可你不給我地圖(調試信息),我咋知道問題在哪?”解決方法:編譯的時候記得加上 -g 參數(shù),這是讓 GCC 幫你把調試信息打包進去,比如:gcc -g main.c -o main
這個 -g 就是那個“地圖”。
然后再用 GDB 調試:
gdb ./main
這下 GDB 才知道代碼怎么走的,可以幫你查問題了!
8. 總結:gcc/g++/gdb 不是魔法,用熟了像開掛gcc/g++ 就像開發(fā)中的“瑞士軍刀”,功能全面卻不復雜,用熟了它們,開發(fā)效率會直線上升:
從編譯到優(yōu)化,輕松搞定,讓程序又快又穩(wěn)。從調試到排錯,有了 gdb,分析問題更加清晰直觀。掌握了 gcc/g++ 和 gdb,任何 C/C++ 程序編譯調試都不在話下!看完這篇文章,你是不是覺得 gcc/g++ 和 gdb 的用法更清晰了?趕緊上手操作一下吧,實踐是掌握工具的最快捷徑!有任何疑問,歡迎在評論區(qū)留言,小康會陪你一起解決! 記得點贊和在看,讓更多人看到這篇干貨文章!
end
一口Linux
關注,回復【1024】海量Linux資料贈送
精彩文章合集
文章推薦
?【專輯】ARM?【專輯】粉絲問答?【專輯】所有原創(chuàng)?【專輯】linux入門?【專輯】計算機網(wǎng)絡?【專輯】Linux驅動?【干貨】嵌入式驅動工程師學習路線?【干貨】Linux嵌入式所有知識點-思維導圖 |
|