文章一覽
- 前言
- 一、gcc編譯系統
- 1.1 文件名后綴
- 1.2 C語言編譯過程
- 1.3 gcc命令行選項
- 二、gdb程序調試工具
- 2.1 啟動gdb和查看內部命令
- 2.2 顯示源程序和數據
- 2.2.1 顯示和搜索源程序
- 2.2.2 查看運行時數據
- 2.3 改變和顯示目錄或路徑
- 2.4 控制程序的執行
- 2.4.1 設置斷點
- 2.4.2 顯示斷點
- 2.4.3 刪除斷點
- 2.4.4 運行程序
- 2.4.5 程序的單步跟蹤和連續執行
- 2.4.6 函數調用
- 2.5 其他常用命令
前言
在數字世界的浩瀚星海中,Linux操作系統如同一顆璀璨的恒星,以其開源、穩定和強大的特性,照亮了無數開發者的編程之路。而C語言,作為最接近硬件層面的高級編程語言,以其無與倫比的性能和靈活性,成為了構建Linux系統的基石。在這個充滿挑戰與機遇的時代,掌握Linux環境下的C程序設計,不僅是技術追求,更是一種對極致性能和系統控制的渴望。
現在,讓我們一起啟程,深入Linux與C語言的神秘世界。
一、gcc編譯系統
1.1 文件名后綴
目前Linux平臺上最常用的C語言編譯系統是gcc(GNU Compiler Collection)
常用文件名后綴及其表示的文件類型:
文件名后綴 | 文件類型 |
---|---|
.c | C源文件 |
.i | 預處理后的C源文件 |
.ii | 預處理后的C++源文件 |
.h | C或C++頭文件 |
.C .cc .cp .cpp .c++ .cxx | C++源文件 |
.s | 匯編程序文件 |
.S | 必須預處理的匯編程序文件 |
.o | 目標文件 |
.a | 靜態鏈接庫 |
.so | 動態鏈接庫 |
1.2 C語言編譯過程
-
預處理階段預處理程序(Preprocessor)讀取C語言源文件,對其中以“#”開頭的指令(偽指令)和特殊符號進行處理。 偽指令主要包括文件包含、宏定義和條件編譯指令。
-
編譯階段編譯程序(Compiler)對預處理之后的輸出文件進行詞法分析和語法分析,試圖找出所有不符合語法規則的部分。在確定各成分都符合語法規則后,將其“翻譯”為功能等價的中間代碼表示或者匯編代碼。
-
匯編過程匯編程序(Assembler)把匯編語言代碼翻譯成目標機器代碼的過程。
-
連接階段將一個文件中引用的符號(如變量或函數調用)與該符號在另外一個文件中的定義連接起來,從而使有關的目標文件連成一個整體,最終成為可被操作系統執行的可執行文件。連接模式分為靜態連接和動態連接。
1.3 gcc命令行選項
$ gcc f1.c f2.c
(針對C語言源程序) 執行完成后,生成默認的可執行文件a.out。
按照選項作用所對應的編譯階段,可將gcc的選項分為四組:預處理選項、編譯選項、優化選項和連接選項。
- 預處理選項
選項格式 | 功能 |
---|---|
-C | 在預處理后的輸出中保留源文件中的注釋 |
-D name | 預定義一個宏name,而且其值為1 |
-D name=definition | 預定義一個宏name,并指定其值為definition所指定的值。其作用等價于在源文件中使用宏定義指令:#define name definition。但-D選項比宏定義指令的優先級高,它可以覆蓋源文件中的定義 |
-U name | 取消先前對name的任何定義,不管是內置的,還是由-D選項提供的 |
-I dir | 指定搜索頭文件的路徑dir。先在指定的路徑中搜索要包含的頭文件,若找不到,則在標準路徑(/usr/include, /usr/lib及當前工作目錄)上搜索 |
-E | 只對指定的源文件進行預處理,不做編譯,生成的結果送到標準輸出 |
例:
# 添加“-I /root”參數編譯,/root為my.h文件位置gcc 1-3.c -o 1-3 -I /root
- 編譯程序選項
選項格式 | 功能 |
---|---|
-c | 只生成目標文件,不進行連接。用于對源文件的分別編譯 |
-S | 只進行編譯,不做匯編,生成匯編代碼文件格式,其名與源文件相同,但擴展名為.s |
-o file | 將輸出放在文件file中。如果未使用該選項,則可執行文件放在a.out中 |
-g | 指示編譯程序在目標代碼中加入供調試程序gdb使用的附加信息 |
-v | 在標準出錯輸出上顯示編譯階段所執行的命令,即編譯驅動程序及預處理程序的版本號 |
- 優化程序選項
優化分為對中間代碼的優化和針對目標代碼生成的優化。
- 連接程序選項
選項格式 | 功能 |
---|---|
object -file -name | 不以專用后綴結尾的文件名就認為是目標文件名或庫名。連接程序可以根據文件內容來區分目標文件和庫 |
-c -S -E | 如果使用其中任何一個選項,那么都不運行連接程序,而且目標文件名不應該用做參數 |
-llibrary | 連接時搜索由library命名的庫。連接程序按照在命令行上給定的順序搜索和處理庫及目標文件。實際的庫名是liblibrary.a |
-static | 在支持動態連接的系統中,它強制使用靜態鏈接庫,而阻止連接動態庫;而在其他系統中不起作用 |
-Ldir | 把指定的目錄dir加到連接程序搜索庫文件的路徑表中,即在搜索-l后面列舉的庫文件時,首先到dir下搜索,找不到再到標準位置下搜索 |
-Bprefix | 該選項規定在什么地方查找可執行文件、庫文件、包含文件和編譯程序本身數據文件 |
-o file | 指定連接程序最后生成的可執行文件名稱為file,不是默認的a.out |
-
Linux下庫文件的命名有一個約定,所有的庫名都以lib開頭。因此,在-l選項所指定的文件名前自動地插入lib。并且約定,以.a(歸檔,archive)結尾的庫是靜態庫,以**.so(共享目標,shared object)結尾的庫是動態庫**。
-
生成靜態庫的方法實際上可分為兩步:
① 將各函數的源文件編譯成目標文件。例如:
\$ gcc -c f1.c f2.c f3.c -o game.o
由此可得到各源文件的目標文件game.o。
② 使用ar工具將目標文件收集起來,放到一個歸檔文件中。例如:
\$ ar -rcs \$HOME/lib/libgame.a game.o
- 生成靜態庫以后,就可在編譯C語言源文件時指明對它進行搜索、連接,例如:
\$ gcc f1.c f2.c f3.c -o mygame -static -L$HOME/lib -lgame
例:
gcc編譯靜態庫
#gcc 編譯靜態庫并調用,演示如何將 add.c 打包成一個靜態庫來供 main.c 調用.
#1.首先將add.c編譯成目標文件。
gcc -c add.c -o add.o
# 2.ar 打包靜態庫,將 add.o 打包成 libadd.a,
#參數 [-crv] :–c表示建立備存文件,- r表示將文件插入備存文件中,-v表示程序執行時顯示詳細的信息
ar -crv libadd.a add.o
#3.使用靜態庫 libadd.a編譯main.c, [-L./] 表示將當前目錄加到靜態庫的搜索路徑
gcc main.c -L./ libadd.a -o main2
#4.執行
./main2
gcc 編譯動態庫
#1.編譯位置無關的目標文件 add.o,因為動態庫動態加載到內存中的位置不確定,所以需要編譯位置無關
gcc -fPIC -c add.c -o add.o
打包成動態庫,
#2.如果不加 sudo 會提示沒有權限,如果不指定/usr/lib/文件夾,會在執行時提示
#error while loading shared libraries:libadd.so: cannot open shared object file: No such file or directory
sudo gcc -shared add.o -o /usr/lib/libadd.so
#3.編譯
sudo gcc -0 main3 main.c -L /usr/lib/ -ladd
#4.執行
./main3
二、gdb程序調試工具
程序中的錯誤按其性質可分為三種:
(1)編譯錯誤,即語法錯誤。主要是程序代碼中有不符合所用編程語言語法規則的錯誤。
(2)運行錯誤。如對負數開平方,除數為0,循環終止條件永遠不能達到等 。
(3)邏輯錯誤。這類錯誤往往是編程前對求解的問題理解不正確或算法不正確引起的,它們很難查找。查找程序中的錯誤,診斷其準確位置,并予以改正,這就是程序調試。程序調試分為人工查錯與機器調試。
gdb主要幫助用戶在調試程序時完成四方面的工作:
(1)啟動程序,可以按用戶要求影響程序的運行行為。
(2)使運行程序在指定條件處停止。
(3)當程序停止時,檢查它出現了什么問題。
(4)動態改變程序的執行環境,這樣就可以糾正一個錯誤的影響,然后再糾正其他錯誤。
2.1 啟動gdb和查看內部命令
為了發揮gdb的全部功能,需要在編譯源程序時使用-g選項:
gcc -g prog.c -o prog #(針對C語言源程序prog.c)
gcc -g program.cpp -o program # (針對C++源程序program.cpp)
啟動gdb的常用方法有:
(1)以一個可執行程序作為gdb的參數:
$ gdb prgm
(2)同時以可執行程序和core文件作為gdb的參數:
$ gdb prgm core
啟動gdb后就顯示其提示符:(gdb),并等待用戶輸入相應的內部命令。
用戶可以利用命令quit終止其執行,退出gdb環境。
2.2 顯示源程序和數據
2.2.1 顯示和搜索源程序
(1)顯示源文件
利用list命令可以顯示源文件中指定的函數或代碼行。
格式 | 功能 |
---|---|
list | 沒有參數,顯示當前行之后或周圍的10多行 |
list – | 顯示先前10行之前的10行 |
list [file:] num | 顯示源文件file中給定行號num周圍的10行。如果缺少file,則默認為當前文件。例如,list 100 |
list start , end | 顯示從行號start至end之間的代碼行。例如,list 20,38 |
list [file:]function | 顯示源文件file中指定函數function的代碼行。如果缺少file,則默認為當前文件。例如,list meng1.c:square |
也可以利用set listsize命令重新設置一次顯示源程序的行數:
set listsize linenum
(2)模式搜索
格式 | 功能 |
---|---|
forward-search regexp | 從列出的最后一行開始向前搜索給定的模式regexp(即正則表達式,一個字符串的匹配模式)。例如,forward-search i=* |
search regexp | 同上 |
reverse-search regexp | 從列出的最后一行開始向后搜索給定的模式regexp(即正則表達式,一個字符串的匹配模式)。例如,reverse-search i=?? |
2.2.2 查看運行時數據
(1)print命令
- 當被調試的程序停止時,可以用print命令(簡寫為p)或同義命令inspect來查看當前程序中運行的數據。
- print命令的一般使用格式:
print [/fmt] exp
? print i (或p i)
顯示當前變量i的值。
? print i\*j (或p i\*j)
將根據程序當前運行的實際情況顯示出i*j的值。
(2)gdb所支持的運算符
①用&運算符取出變量在內存中的地址,如:
? print &i 顯示變量i的存放地址。
? print &array[i] 顯示數組array第i個元素的地址。
② { type }adrexp 表示一個數據類型為type、存放地址為adrexp的數據。
③ @ 是一個與數組有關的雙目運算符,使用形式如:
? print array@10
打印從array(數組名,即數組的基地址)開始的10個值。
? print array[3]@5
打印從array第三個元素開始的5個數組元素的數值。
④file :: var
(或者 function :: var ) 表示文件file(或者函數function)中變量var的值。例如:
? print inner::i 打印函數inner中變量i的當前值。
(3)輸出格式
由表示格式的字母(如o、x、d、u、t、f、a、i、c、s)和表示數據長度的字母(如b、w、h、g)組成。
2.3 改變和顯示目錄或路徑
(1)directory命令
? 將給定目錄dir添加到源文件搜索路徑的開頭,并且忽略先前保存的有關源文件和代碼行位置的信息。其一般格式是:
? directory [dir] 或者 dir [dir]
(2)cd命令
? cd命令將調試程序和被調試程序的工作目錄置為指定的目錄dir。其使用格式為:
? cd dir
(3)path命令
? 利用path命令可以將一個或多個目錄添加到目標文件搜索路徑的開頭。其使用格式是:
? path dirs
(4)pwd命令
? 該命令用來顯示工作目錄。
(5)show directories命令
? 該命令顯示定義的源文件搜索路徑。
(6)show paths命令
? 該命令顯示當前查找目標文件的搜索路徑。
2.4 控制程序的執行
2.4.1 設置斷點
編譯源程序時需要使用-g選項
在gdb中用break命令(其縮寫形式為b)設置斷點:
● break linenum
(在當前文件指定行linenum處設置斷點,停在該行開頭)
● break linenum if condition
(在當前文件指定行linenum處設置斷點,但僅在條件表達式condition成立時才停止程序執行)
● break function
(在當前文件函數function的入口處設置斷點)
● break file:linenum
(在源文件file的linenum行上設置斷點)
● break file:function
(在源文件file的函數function的入口處設置斷點)
● break *address
(運行程序在指定的內存地址address處停止)
● break
(不帶任何參數,則表示在下一條指令處停止)
斷點應設置在可執行的行上,不應是變量定義之類的語句。
2.4.2 顯示斷點
info breakpoints [num]
info break [num]
2.4.3 刪除斷點
delete [bkptnums]
2.4.4 運行程序
run [args] (run簡寫是r)
2.4.5 程序的單步跟蹤和連續執行
(1)單步跟蹤
? step [N] 參數N表示每步執行的語句行數。 進入被調用函數內部執行。
? next [N] 參數N表示每步執行的語句行數。 被調用函數被當做一條指令執行。
? stepi(縮寫為si)或nexti(縮寫為ni)命令一條一條地執行機器指令。
(2)連續執行
利用continue,c或fg命令連續執行到下一個斷點
2.4.6 函數調用
call expr
? 其中,expr是所用編程語言的函數調用表達式,包括函數名和實參。
- 在調試過程中,可以使用return命令強行從正在執行的函數中退出:
? return [expr]
- 還可以使用finish命令退出函數,但它并不立即退出,而是繼續運行,直至當前函數返回
2.5 其他常用命令
1.執行shell命令
shell command-string
例如:
(gdb) shell date
2009年 03月 31日 星期二 16:47:56 CST
(gdb)
2.修改變量值
(gdb) print x=10
(gdb) set variable x=10
3.跳轉執行
jump linenum (參數linenum表示下一條語句的行號。)
jump *addr (參數 addr表示下一條代碼行的內存地址。)