gcc选项 和 gdb 使用
CC 編譯詳解
GNU CC(簡(jiǎn)稱為Gcc)是GNU項(xiàng)目中符合ANSI C標(biāo)準(zhǔn)的編譯系統(tǒng),能夠編譯用C、C++和Object C等語(yǔ)言編寫的程序。Gcc不僅功能強(qiáng)大,而且可以編譯如C、C++、Object C、Java、Fortran、Pascal、Modula-3和Ada等多種語(yǔ)言,而且Gcc又是一個(gè)交叉平臺(tái)編譯器,它能夠在當(dāng)前CPU平臺(tái)上為多種不同體系結(jié)構(gòu)的硬件平臺(tái)開發(fā)軟件,因此尤其適合在嵌入式領(lǐng)域的開發(fā)編譯。本章中的示例,除非特別注明,否則均采用Gcc版本為4.0.0。
?
?
GCC入門基礎(chǔ)
表3.6 Gcc所支持后綴名解釋
| 后 綴 名 | 所對(duì)應(yīng)的語(yǔ)言 | 后 綴 名 | 所對(duì)應(yīng)的語(yǔ)言 |
| .c | C原始程序 | .s/.S | 匯編語(yǔ)言原始程序 |
| .C/.cc/.cxx | C++原始程序 | .h | 預(yù)處理文件(頭文件) |
| .m | Objective-C原始程序 | .o | 目標(biāo)文件 |
| .i | 已經(jīng)過預(yù)處理的C原始程序 | .a/.so | 編譯后的庫(kù)文件 |
| .ii | 已經(jīng)過預(yù)處理的C++原始程序 | ? | ? |
如本章開頭提到的,Gcc的編譯流程分為了四個(gè)步驟,分別為:
· 預(yù)處理(Pre-Processing)
· 編譯(Compiling)
· 匯編(Assembling)
· 鏈接(Linking)
下面就具體來查看一下Gcc是如何完成四個(gè)步驟的。
首先,有以下hello.c源代碼
#include<stdio.h>
int main()
{
printf("Hello! This is our embedded world!n");
return 0;
}
(1)預(yù)處理階段
在該階段,編譯器將上述代碼中的stdio.h編譯進(jìn)來,并且用戶可以使用Gcc的選項(xiàng)”-E”進(jìn)行查看,該選項(xiàng)的作用是讓Gcc在預(yù)處理結(jié)束后停止編譯過程。
| ? | 注意 | Gcc指令的一般格式為:Gcc [選項(xiàng)] 要編譯的文件 [選項(xiàng)] [目標(biāo)文件] 其中,目標(biāo)文件可缺省,Gcc默認(rèn)生成可執(zhí)行的文件,命為:編譯文件.out |
?
[root@localhost Gcc]#?Gcc –E hello.c –o hello.i
?
在此處,選項(xiàng)”-o”是指目標(biāo)文件,由表3.6可知,”.i”文件為已經(jīng)過預(yù)處理的C原始程序。以下列出了hello.i文件的部分內(nèi)容:
?
typedef int (*__gconv_trans_fct) (struct __gconv_step *,
struct __gconv_step_data *, void *,
__const unsigned char *,
__const unsigned char **,
__const unsigned char *, unsigned char **,
size_t *);
…
# 2 "hello.c" 2
int main()
{
printf("Hello! This is our embedded world!n");
return 0;
}
?
由此可見,Gcc確實(shí)進(jìn)行了預(yù)處理,它把”stdio.h”的內(nèi)容插入到hello.i文件中。
(2)編譯階段
接下來進(jìn)行的是編譯階段,在這個(gè)階段中,Gcc首先要檢查代碼的規(guī)范性、是否有語(yǔ)法錯(cuò)誤等,以確定代碼的實(shí)際要做的工作,在檢查無誤后,Gcc把代碼翻譯成匯編語(yǔ)言。用戶可以使用”-S”選項(xiàng)來進(jìn)行查看,該選項(xiàng)只進(jìn)行編譯而不進(jìn)行匯編,生成匯編代碼。
?
[root@localhost Gcc]# Gcc –S hello.i –o hello.s
?
以下列出了hello.s的內(nèi)容,可見Gcc已經(jīng)將其轉(zhuǎn)化為匯編了,感興趣的讀者可以分析一下這一行簡(jiǎn)單的C語(yǔ)言小程序是如何用匯編代碼實(shí)現(xiàn)的。
?
.file "hello.c"
.section .rodata
.align 4
.LC0:
.string"Hello! This is our embedded world!"
.text
.globl main
.type main, @function
main:
pushl �p
movl %esp, �p
subl $8, %esp
andl $-16, %esp
movl $0, �x
addl $15, �x
addl $15, �x
shrl $4, �x
sall $4, �x
subl �x, %esp
subl $12, %esp
pushl $.LC0
call puts
addl $16, %esp
movl $0, �x
leave
ret
.size main, .-main
.ident "GCC: (GNU) 4.0.0 20050519 (Red Hat 4.0.0-8)"
.section .note.GNU-stack,"",@progbits
?
(3)匯編階段
匯編階段是把編譯階段生成的”.s”文件轉(zhuǎn)成目標(biāo)文件,讀者在此可使用選項(xiàng)”-c”就可看到匯編代碼已轉(zhuǎn)化為”.o”的二進(jìn)制目標(biāo)代碼了。如下所示:
?
[root@localhost Gcc]# Gcc –c?hello.s –o hello.o
?
(4)鏈接階段
在成功編譯之后,就進(jìn)入了鏈接階段。在這里涉及到一個(gè)重要的概念:函數(shù)庫(kù)。
讀者可以重新查看這個(gè)小程序,在這個(gè)程序中并沒有定義”printf”的函數(shù)實(shí)現(xiàn),且在預(yù)編譯中包含進(jìn)的”stdio.h”中也只有該函數(shù)的聲明,而沒有定義函數(shù)的實(shí)現(xiàn),那么,是在哪里實(shí)現(xiàn)”printf”函數(shù)的呢?最后的答案是:系統(tǒng)把這些函數(shù)實(shí)現(xiàn)都被做到名為libc.so.6的庫(kù)文件中去了,在沒有特別指定時(shí),Gcc會(huì)到系統(tǒng)默認(rèn)的搜索路徑”/usr/lib”下進(jìn)行查找,也就是鏈接到libc.so.6庫(kù)函數(shù)中去,這樣就能實(shí)現(xiàn)函數(shù)”printf”了,而這也就是鏈接的作用。
函數(shù)庫(kù)一般分為靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)兩種。靜態(tài)庫(kù)是指編譯鏈接時(shí),把庫(kù)文件的代碼全部加入到可執(zhí)行文件中,因此生成的文件比較大,但在運(yùn)行時(shí)也就不再需要庫(kù)文件了。其后綴名一般為”.a”。動(dòng)態(tài)庫(kù)與之相反,在編譯鏈接時(shí)并沒有把庫(kù)文件的代碼加入到可執(zhí)行文件中,而是在程序執(zhí)行時(shí)由運(yùn)行時(shí)鏈接文件加載庫(kù),這樣可以節(jié)省系統(tǒng)的開銷。動(dòng)態(tài)庫(kù)一般后綴名為”.so”,如前面所述的libc.so.6就是動(dòng)態(tài)庫(kù)。Gcc在編譯時(shí)默認(rèn)使用動(dòng)態(tài)庫(kù)。
完成了鏈接之后,Gcc就可以生成可執(zhí)行文件,如下所示。
?
[root@localhost Gcc]# Gcc hello.o –o hello
?
運(yùn)行該可執(zhí)行文件,出現(xiàn)正確的結(jié)果如下。
?
[root@localhost Gcc]# ./hello
Hello! This is our embedded world!
Gcc編譯選項(xiàng)分析
Gcc有超過100個(gè)的可用選項(xiàng),主要包括總體選項(xiàng)、告警和出錯(cuò)選項(xiàng)、優(yōu)化選項(xiàng)和體系結(jié)構(gòu)相關(guān)選項(xiàng)。以下對(duì)每一類中最常用的選項(xiàng)進(jìn)行講解。
(1)總體選項(xiàng)
Gcc的總結(jié)選項(xiàng)如表3.7所示,很多在前面的示例中已經(jīng)有所涉及。
表3.7 Gcc總體選項(xiàng)列表
| 后綴名 | 所對(duì)應(yīng)的語(yǔ)言 |
| -c | 只是編譯不鏈接,生成目標(biāo)文件“.o” |
| -S | 只是編譯不匯編,生成匯編代碼 |
| -E | 只進(jìn)行預(yù)編譯,不做其他處理 |
| -g | 在可執(zhí)行程序中包含標(biāo)準(zhǔn)調(diào)試信息 |
| -o file | 把輸出文件輸出到file里 |
| -v | 打印出編譯器內(nèi)部編譯各過程的命令行信息和編譯器的版本 |
| -I dir | 在頭文件的搜索路徑列表中添加dir目錄 |
| -L dir | 在庫(kù)文件的搜索路徑列表中添加dir目錄 |
| -static | 鏈接靜態(tài)庫(kù) |
| -llibrary | 連接名為library的庫(kù)文件 |
?
對(duì)于“-c”、“-E”、“-o”、“-S”選項(xiàng)在前一小節(jié)中已經(jīng)講解了其使用方法,在此主要講解另外兩個(gè)非常常用的庫(kù)依賴選項(xiàng)“-I dir”和“-L dir”。
· “-I dir”
正如上表中所述,“-I dir”選項(xiàng)可以在頭文件的搜索路徑列表中添加dir目錄。由于Linux中頭文件都默認(rèn)放到了“/usr/include/”目錄下,因此,當(dāng)用戶希望添加放置在其他位置的頭文件時(shí),就可以通過“-I dir”選項(xiàng)來指定,這樣,Gcc就會(huì)到相應(yīng)的位置查找對(duì)應(yīng)的目錄。
比如在“/root/workplace/Gcc”下有兩個(gè)文件:
?
?
#include<my.h>
int main()
{
printf(“Hello!!n”);
return 0;
}
?
#include<stdio.h>
?
這樣,就可在Gcc命令行中加入“-I”選項(xiàng):
?
[root@localhost Gcc] Gcc?hello1.c –I /root/workplace/Gcc/ -o hello1
?
這樣,Gcc就能夠執(zhí)行出正確結(jié)果。
?
| 小知識(shí) | 在include語(yǔ)句中,“<>”表示在標(biāo)準(zhǔn)路徑中搜索頭文件,““””表示在本目錄中搜索。故在上例中,可把hello1.c的“#include<my.h>”改為“#include “my.h””,就不需要加上“-I”選項(xiàng)了。 |
?
· “-L dir”
選項(xiàng)“-L dir”的功能與“-I dir”類似,能夠在庫(kù)文件的搜索路徑列表中添加dir目錄。例如有程序hello_sq.c需要用到目錄“/root/workplace/Gcc/lib”下的一個(gè)動(dòng)態(tài)庫(kù)libsunq.so,則只需鍵入如下命令即可:
?
[root@localhost Gcc]?Gcc hello_sq.c –L /root/workplace/Gcc/lib –lsunq –o hello_sq
?
需要注意的是,“-I dir”和“-L dir”都只是指定了路徑,而沒有指定文件,因此不能在路徑中包含文件名。
另外值得詳細(xì)解釋一下的是“-l”選項(xiàng),它指示Gcc去連接庫(kù)文件libsunq.so。由于在Linux下的庫(kù)文件命名時(shí)有一個(gè)規(guī)定:必須以lib三個(gè)字母開頭。因此在用-l選項(xiàng)指定鏈接的庫(kù)文件名時(shí)可以省去lib三個(gè)字母。也就是說Gcc在對(duì)”-lsunq”進(jìn)行處理時(shí),會(huì)自動(dòng)去鏈接名為libsunq.so的文件。
(2)告警和出錯(cuò)選項(xiàng)
Gcc的告警和出錯(cuò)選項(xiàng)如表3.8所示。
表3.8 Gcc總體選項(xiàng)列表
| 選項(xiàng) | 含義 |
| -ansi | 支持符合ANSI標(biāo)準(zhǔn)的C程序 |
| -pedantic | 允許發(fā)出ANSI C標(biāo)準(zhǔn)所列的全部警告信息 |
| 選項(xiàng) | 含義 |
| -pedantic-error | 允許發(fā)出ANSI C標(biāo)準(zhǔn)所列的全部錯(cuò)誤信息 |
| -w | 關(guān)閉所有告警 |
| -Wall | 允許發(fā)出Gcc提供的所有有用的報(bào)警信息 |
| -werror | 把所有的告警信息轉(zhuǎn)化為錯(cuò)誤信息,并在告警發(fā)生時(shí)終止編譯過程 |
?
下面結(jié)合實(shí)例對(duì)這幾個(gè)告警和出錯(cuò)選項(xiàng)進(jìn)行簡(jiǎn)單的講解。
如有以下程序段:
?
#include<stdio.h>
?
void main()
{
long long tmp = 1;
printf(“This is a bad code!n”);
return 0;
}
?
這是一個(gè)很糟糕的程序,讀者可以考慮一下有哪些問題?
· “-ansi”
該選項(xiàng)強(qiáng)制Gcc生成標(biāo)準(zhǔn)語(yǔ)法所要求的告警信息,盡管這還并不能保證所有沒有警告的程序都是符合ANSI C標(biāo)準(zhǔn)的。運(yùn)行結(jié)果如下所示:
?
[root@localhost Gcc]# Gcc?–ansi warning.c –o warning
warning.c: 在函數(shù)“main”中:
warning.c:7 警告:在無返回值的函數(shù)中,“return”帶返回值
warning.c:4 警告:“main”的返回類型不是“int”
?
可以看出,該選項(xiàng)并沒有發(fā)現(xiàn)”long long”這個(gè)無效數(shù)據(jù)類型的錯(cuò)誤。
· “-pedantic”
允許發(fā)出ANSI C標(biāo)準(zhǔn)所列的全部警告信息,同樣也保證所有沒有警告的程序都是符合ANSI C標(biāo)準(zhǔn)的。其運(yùn)行結(jié)果如下所示:
?
[root@localhost Gcc]# Gcc –pedantic warning.c –o warning
warning.c: 在函數(shù)“main”中:
warning.c:5 警告:ISO C90不支持“l(fā)ong long”
warning.c:7 警告:在無返回值的函數(shù)中,“return”帶返回值
warning.c:4 警告:“main”的返回類型不是“int”
?
可以看出,使用該選項(xiàng)查看出了”long long”這個(gè)無效數(shù)據(jù)類型的錯(cuò)誤。
· “-Wall”
允許發(fā)出Gcc能夠提供的所有有用的報(bào)警信息。該選項(xiàng)的運(yùn)行結(jié)果如下所示:
[root@localhost Gcc]# Gcc?–Wall warning.c –o warning
warning.c:4 警告:“main”的返回類型不是“int”
warning.c: 在函數(shù)”main”中:
warning.c:7 警告:在無返回值的函數(shù)中,”return”帶返回值
warning.c:5 警告:未使用的變量“tmp”
?
使用“-Wall”選項(xiàng)找出了未使用的變量tmp,但它并沒有找出無效數(shù)據(jù)類型的錯(cuò)誤。
另外,Gcc還可以利用選項(xiàng)對(duì)單獨(dú)的常見錯(cuò)誤分別指定警告,有關(guān)具體選項(xiàng)的含義感興趣的讀者可以查看Gcc手冊(cè)進(jìn)行學(xué)習(xí)。
(3)優(yōu)化選項(xiàng)
Gcc可以對(duì)代碼進(jìn)行優(yōu)化,它通過編譯選項(xiàng)“-On”來控制優(yōu)化代碼的生成,其中n是一個(gè)代表優(yōu)化級(jí)別的整數(shù)。對(duì)于不同版本的Gcc來講,n的取值范圍及其對(duì)應(yīng)的優(yōu)化效果可能并不完全相同,比較典型的范圍是從0變化到2或3。
不同的優(yōu)化級(jí)別對(duì)應(yīng)不同的優(yōu)化處理工作。如使用優(yōu)化選項(xiàng)“-O”主要進(jìn)行線程跳轉(zhuǎn)(Thread Jump)和延遲退棧(Deferred Stack Pops)兩種優(yōu)化。使用優(yōu)化選項(xiàng)“-O2”除了完成所有“-O1”級(jí)別的優(yōu)化之外,同時(shí)還要進(jìn)行一些額外的調(diào)整工作,如處理器指令調(diào)度等。選項(xiàng)“-O3”則還包括循環(huán)展開和其他一些與處理器特性相關(guān)的優(yōu)化工作。
雖然優(yōu)化選項(xiàng)可以加速代碼的運(yùn)行速度,但對(duì)于調(diào)試而言將是一個(gè)很大的挑戰(zhàn)。因?yàn)榇a在經(jīng)過優(yōu)化之后,原先在源程序中聲明和使用的變量很可能不再使用,控制流也可能會(huì)突然跳轉(zhuǎn)到意外的地方,循環(huán)語(yǔ)句也有可能因?yàn)檠h(huán)展開而變得到處都有,所有這些對(duì)調(diào)試來講都將是一場(chǎng)噩夢(mèng)。所以筆者建議在調(diào)試的時(shí)候最好不使用任何優(yōu)化選項(xiàng),只有當(dāng)程序在最終發(fā)行的時(shí)候才考慮對(duì)其進(jìn)行優(yōu)化。
(4)體系結(jié)構(gòu)相關(guān)選項(xiàng)
Gcc的體系結(jié)構(gòu)相關(guān)選項(xiàng)如表3.9所示。
表3.9Gcc體系結(jié)構(gòu)相關(guān)選項(xiàng)列表
| 選項(xiàng) | 含義 |
| -mcpu=type | 針對(duì)不同的CPU使用相應(yīng)的CPU指令。可選擇的type有i386、i486、pentium及i686等 |
| -mieee-fp | 使用IEEE標(biāo)準(zhǔn)進(jìn)行浮點(diǎn)數(shù)的比較 |
| -mno-ieee-fp | 不使用IEEE標(biāo)準(zhǔn)進(jìn)行浮點(diǎn)數(shù)的比較 |
| -msoft-float | 輸出包含浮點(diǎn)庫(kù)調(diào)用的目標(biāo)代碼 |
| -mshort | 把int類型作為16位處理,相當(dāng)于short int |
| -mrtd | 強(qiáng)行將函數(shù)參數(shù)個(gè)數(shù)固定的函數(shù)用ret NUM返回,節(jié)省調(diào)用函數(shù)的一條指令 |
?
這些體系結(jié)構(gòu)相關(guān)選項(xiàng)在嵌入式的設(shè)計(jì)中會(huì)有較多的應(yīng)用,讀者需根據(jù)不同體系結(jié)構(gòu)將對(duì)應(yīng)的選項(xiàng)進(jìn)行組合處理。在本書后面涉及到具體實(shí)例會(huì)有針對(duì)性的講解。
Gdb調(diào)試器
調(diào)試是所有程序員都會(huì)面臨的問題。如何提高程序員的調(diào)試效率,更好更快地定位程序中的問題從而加快程序開發(fā)的進(jìn)度,是大家共同面對(duì)的。就如讀者熟知的Windows下的一些調(diào)試工具,如VC自帶的如設(shè)置斷點(diǎn)、單步跟蹤等,都受到了廣大用戶的贊賞。那么,在Linux下有什么很好的調(diào)試工具呢?
本文所介紹的Gdb調(diào)試器是一款GNU開發(fā)組織并發(fā)布的UNIX/Linux下的程序調(diào)試工具。雖然,它沒有圖形化的友好界面,但是它強(qiáng)大的功能也足以與微軟的VC工具等媲美。下面就請(qǐng)跟隨筆者一步步學(xué)習(xí)Gdb調(diào)試器。
Gdb使用流程
首先,筆者給出了一個(gè)短小的程序,由此帶領(lǐng)讀者熟悉一下Gdb的使用流程。強(qiáng)烈建議讀者能夠?qū)嶋H動(dòng)手操作。
首先,打開Linux下的編輯器Vi或者Emacs,編輯如下代碼。(由于為了更好地熟悉Gdb的操作,筆者在此使用Vi編輯,希望讀者能夠參見3.3節(jié)中對(duì)Vi的介紹,并熟練使用Vi)。
?
?
#include <stdio.h>
int sum(int m);
int main()
{
int i,n=0;
sum(50);
for(i=1; i<=50; i++)
{
n += i;
}
printf("The sum of 1-50 is %d n", n );
?
}
int sum(int m)
{
int i,n=0;
for(i=1; i<=m;i++)
n += i;
printf("The sum of 1-m is %dn", n);
}
?
在保存退出后首先使用Gcc對(duì)test.c進(jìn)行編譯,注意一定要加上選項(xiàng)”-g”,這樣編譯出的可執(zhí)行代碼中才包含調(diào)試信息,否則之后Gdb無法載入該可執(zhí)行文件。
?
[root@localhost Gdb]#?gcc -g test.c -o test
?
雖然這段程序沒有錯(cuò)誤,但調(diào)試完全正確的程序可以更加了解Gdb的使用流程。接下來就啟動(dòng)Gdb進(jìn)行調(diào)試。注意,Gdb進(jìn)行調(diào)試的是可執(zhí)行文件,而不是如”.c”的源代碼,因此,需要先通過Gcc編譯生成可執(zhí)行文件才能用Gdb進(jìn)行調(diào)試。
?
[root@localhost Gdb]#?gdb test
GNU Gdb Red Hat Linux (6.3.0.0-1.21rh)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".
(gdb)
?
可以看出,在Gdb的啟動(dòng)畫面中指出了Gdb的版本號(hào)、使用的庫(kù)文件等信息,接下來就進(jìn)入了由“(gdb)”開頭的命令行界面了。
(1)查看文件
在Gdb中鍵入”l”(list)就可以查看所載入的文件,如下所示:
?
| ? | 注意 | 在Gdb的命令中都可使用縮略形式的命令,如“l(fā)”代便“l(fā)ist”,“b”代表“breakpoint”,“p”代表“print”等,讀者也可使用“help”命令查看幫助信息。 |
?
(Gdb)?l
1 #include <stdio.h>
2 int sum(int m);
3 int main()
4 {
5 int i,n=0;
6 sum(50);
7 for(i=1; i<=50; i++)
8 {
9 n += i;
10 }
(Gdb)?l
11 printf("The sum of 1~50 is %d n", n );
12
13 }
14 int sum(int m)
15 {
16 int i,n=0;
17 for(i=1; i<=m;i++)
18 n += i;
19 printf("The sum of 1~m is = %dn", n);
20 }
?
可以看出,Gdb列出的源代碼中明確地給出了對(duì)應(yīng)的行號(hào),這樣就可以大大地方便代碼的定位。
(2)設(shè)置斷點(diǎn)
設(shè)置斷點(diǎn)是調(diào)試程序中是一個(gè)非常重要的手段,它可以使程序到一定位置暫停它的運(yùn)行。因此,程序員在該位置處可以方便地查看變量的值、堆棧情況等,從而找出代碼的癥結(jié)所在。
在Gdb中設(shè)置斷點(diǎn)非常簡(jiǎn)單,只需在”b”后加入對(duì)應(yīng)的行號(hào)即可(這是最常用的方式,另外還有其他方式設(shè)置斷點(diǎn))。如下所示:
?
(Gdb)?b 6
Breakpoint 1 at 0x804846d: file test.c, line 6.
?
要注意的是,在Gdb中利用行號(hào)設(shè)置斷點(diǎn)是指代碼運(yùn)行到對(duì)應(yīng)行之前將其停止,如上例中,代碼運(yùn)行到第五行之前暫停(并沒有運(yùn)行第五行)。
(3)查看斷點(diǎn)情況
在設(shè)置完斷點(diǎn)之后,用戶可以鍵入”info b”來查看設(shè)置斷點(diǎn)情況,在Gdb中可以設(shè)置多個(gè)斷點(diǎn)。
?
(Gdb)?info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0804846d in main at test.c:6
?
(4)運(yùn)行代碼
接下來就可運(yùn)行代碼了,Gdb默認(rèn)從首行開始運(yùn)行代碼,可鍵入”r”(run)即可(若想從程序中指定行開始運(yùn)行,可在r后面加上行號(hào))。
?
(Gdb)?r
Starting program: /root/workplace/Gdb/test
Reading symbols from shared object read from target memory...done.
Loaded system supplied DSO at 0x5fb000
?
Breakpoint 1, main () at test.c:6
6 sum(50);
?
可以看到,程序運(yùn)行到斷點(diǎn)處就停止了。
(5)查看變量值
在程序停止運(yùn)行之后,程序員所要做的工作是查看斷點(diǎn)處的相關(guān)變量值。在Gdb中只需鍵入”p”+變量值即可,如下所示:
?
(Gdb)?p n
$1 = 0
(Gdb)?p i
$2 = 134518440
?
在此處,為什么變量”i”的值為如此奇怪的一個(gè)數(shù)字呢?原因就在于程序是在斷點(diǎn)設(shè)置的對(duì)應(yīng)行之前停止的,那么在此時(shí),并沒有把”i”的數(shù)值賦為零,而只是一個(gè)隨機(jī)的數(shù)字。但變量”n”是在第四行賦值的,故在此時(shí)已經(jīng)為零。
?
| 小技巧 | Gdb在顯示變量值時(shí)都會(huì)在對(duì)應(yīng)值之前加上”$N”標(biāo)記,它是當(dāng)前變量值的引用標(biāo)記,所以以后若想再次引用此變量就可以直接寫作”$N”,而無需寫冗長(zhǎng)的變量名。 |
?
(6)單步運(yùn)行
單步運(yùn)行可以使用命令”n”(next)或”s”(step),它們之間的區(qū)別在于:若有函數(shù)調(diào)用的時(shí)候,”s”會(huì)進(jìn)入該函數(shù)而”n”不會(huì)進(jìn)入該函數(shù)。因此,”s”就類似于VC等工具中的”step in”,”n”類似與VC等工具中的”step over”。它們的使用如下所示:
?
(Gdb)?n
The sum of 1-m is 1275
7 for(i=1; i<=50; i++)
(Gdb)?s
sum (m=50) at test.c:16
16 int i,n=0;
?
可見,使用”n”后,程序顯示函數(shù)sum的運(yùn)行結(jié)果并向下執(zhí)行,而使用”s”后則進(jìn)入到sum函數(shù)之中單步運(yùn)行。
(7)恢復(fù)程序運(yùn)行
在查看完所需變量及堆棧情況后,就可以使用命令”c”(continue)恢復(fù)程序的正常運(yùn)行了。這時(shí),它會(huì)把剩余還未執(zhí)行的程序執(zhí)行完,并顯示剩余程序中的執(zhí)行結(jié)果。以下是之前使用”n”命令恢復(fù)后的執(zhí)行結(jié)果:
?
(Gdb)?c
Continuing.
The sum of 1-50 is :1275
?
Program exited with code 031.
?
可以看出,程序在運(yùn)行完后退出,之后程序處于“停止?fàn)顟B(tài)”。
?
| 小知識(shí) | 在Gdb中,程序的運(yùn)行狀態(tài)有“運(yùn)行”、“暫停”和“停止”三種,其中“暫停”狀態(tài)為程序遇到了斷點(diǎn)或觀察點(diǎn)之類的,程序暫時(shí)停止運(yùn)行,而此時(shí)函數(shù)的地址、函數(shù)參數(shù)、函數(shù)內(nèi)的局部變量都會(huì)被壓入“棧”(Stack)中。故在這種狀態(tài)下可以查看函數(shù)的變量值等各種屬性。但在函數(shù)處于“停止”狀態(tài)之后,“棧”就會(huì)自動(dòng)撤銷,它也就無法查看各種信息了。 |
Gdb基本命令
Gdb的命令可以通過查看help進(jìn)行查找,由于Gdb的命令很多,因此Gdb的help將其分成了很多種類(class),用戶可以通過進(jìn)一步查看相關(guān)class找到相應(yīng)命令。如下所示:
?
(gdb)?help
List of classes of commands:
?
aliases -- Aliases of other commands
breakpoints -- Making program stop at certain points
data -- Examining data
files -- Specifying and examining files
internals -- Maintenance commands
…
Type "help" followed by a class name for a list of commands in that class.
Type "help" followed by command name for full documentation.
Command name abbreViations are allowed if unambiguous.
?
上述列出了Gdb各個(gè)分類的命令,注意底部的加粗部分說明其為分類命令。接下來可以具體查找各分類種的命令。如下所示:
?
(gdb) help data
Examining data.
?
List of commands:
?
call -- Call a function in the program
delete display -- Cancel some expressions to be displayed when program stops
delete mem -- Delete memory region
disable display -- Disable some expressions to be displayed when program stops
…
Type "help" followed by command name for full documentation.
Command name abbreViations are allowed if unambiguous.
?
至此,若用戶想要查找call命令,就可鍵入“help call”。
?
(gdb)?help call
Call a function in the program.
The argument is the function name and arguments, in the notation of the
current working language. The result is printed and saved in the value
history, if it is not void.
?
當(dāng)然,若用戶已知命令名,直接鍵入“help [command]”也是可以的。
Gdb中的命令主要分為以下幾類:工作環(huán)境相關(guān)命令、設(shè)置斷點(diǎn)與恢復(fù)命令、源代碼查看命令、查看運(yùn)行數(shù)據(jù)相關(guān)命令及修改運(yùn)行參數(shù)命令。以下就分別對(duì)這幾類的命令進(jìn)行講解。
1.工作環(huán)境相關(guān)命令
Gdb中不僅可以調(diào)試所運(yùn)行的程序,而且還可以對(duì)程序相關(guān)的工作環(huán)境進(jìn)行相應(yīng)的設(shè)定,甚至還可以使用shell中的命令進(jìn)行相關(guān)的操作,其功能極其強(qiáng)大。表3.10所示列出了Gdb常見工作環(huán)境相關(guān)命令。
表3.10 Gdb工作環(huán)境相關(guān)命令
| 命 令 格 式 | 含義 |
| set args運(yùn)行時(shí)的參數(shù) | 指定運(yùn)行時(shí)參數(shù),如:set args 2 |
| show args | 查看設(shè)置好的運(yùn)行參數(shù) |
| path dir | 設(shè)定程序的運(yùn)行路徑 |
| show paths | 查看程序的運(yùn)行路徑 |
| set enVironment var [=value] | 設(shè)置環(huán)境變量 |
| show enVironment [var] | 查看環(huán)境變量 |
| cd dir | 進(jìn)入到dir目錄,相當(dāng)于shell中的cd命令 |
| pwd | 顯示當(dāng)前工作目錄 |
| shell command | 運(yùn)行shell的command命令 |
2.設(shè)置斷點(diǎn)與恢復(fù)命令
Gdb中設(shè)置斷點(diǎn)與恢復(fù)的常見命令如表3.11所示。
表3.11 Gdb設(shè)置斷點(diǎn)與恢復(fù)相關(guān)命令
| 命 令 格 式 | 含義 |
| bnfo b | 查看所設(shè)斷點(diǎn) |
| break 行號(hào)或函數(shù)名 <條件表達(dá)式> | 設(shè)置斷點(diǎn) |
| tbreak 行號(hào)或函數(shù)名 <條件表達(dá)式> | 設(shè)置臨時(shí)斷點(diǎn),到達(dá)后被自動(dòng)刪除 |
| delete [斷點(diǎn)號(hào)] | 刪除指定斷點(diǎn),其斷點(diǎn)號(hào)為”info b”中的第一欄。若缺省斷點(diǎn)號(hào)則刪除所有斷點(diǎn) |
| disable [斷點(diǎn)號(hào)]] | 停止指定斷點(diǎn),使用”info b”仍能查看此斷點(diǎn)。同delete一樣,省斷點(diǎn)號(hào)則停止所有斷點(diǎn) |
| enable [斷點(diǎn)號(hào)] | 激活指定斷點(diǎn),即激活被disable停止的斷點(diǎn) |
| condition [斷點(diǎn)號(hào)] <條件表達(dá)式> | 修改對(duì)應(yīng)斷點(diǎn)的條件 |
| ignore [斷點(diǎn)號(hào)]<num> | 在程序執(zhí)行中,忽略對(duì)應(yīng)斷點(diǎn)num次 |
| step | 單步恢復(fù)程序運(yùn)行,且進(jìn)入函數(shù)調(diào)用 |
| next | 單步恢復(fù)程序運(yùn)行,但不進(jìn)入函數(shù)調(diào)用 |
| finish | 運(yùn)行程序,直到當(dāng)前函數(shù)完成返回 |
| c | 繼續(xù)執(zhí)行函數(shù),直到函數(shù)結(jié)束或遇到新的斷點(diǎn) |
?
由于設(shè)置斷點(diǎn)在Gdb的調(diào)試中非常重要,所以在此再著重講解一下Gdb中設(shè)置斷點(diǎn)的方法。
Gdb中設(shè)置斷點(diǎn)有多種方式:其一是按行設(shè)置斷點(diǎn),設(shè)置方法在3.5.1節(jié)已經(jīng)指出,在此就不重復(fù)了。另外還可以設(shè)置函數(shù)斷點(diǎn)和條件斷點(diǎn),在此結(jié)合上一小節(jié)的代碼,具體介紹后兩種設(shè)置斷點(diǎn)的方法。
① 函數(shù)斷點(diǎn)
Gdb中按函數(shù)設(shè)置斷點(diǎn)只需把函數(shù)名列在命令”b”之后,如下所示:
?
(gdb)?b sum
Breakpoint 1 at 0x80484ba: file test.c, line 16.
(gdb)?info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x080484ba in sum at test.c:16
?
要注意的是,此時(shí)的斷點(diǎn)實(shí)際是在函數(shù)的定義處,也就是在16行處(注意第16行還未執(zhí)行)。
② 條件斷點(diǎn)
Gdb中設(shè)置條件斷點(diǎn)的格式為:b 行數(shù)或函數(shù)名 if 表達(dá)式。具體實(shí)例如下所示:
?
(gdb) b 8 if i==10
Breakpoint 1 at 0x804848c: file test.c, line 8.
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0804848c in main at test.c:8
stop only if i == 10
(gdb) r
Starting program: /home/yul/test
The sum of 1-m is 1275
?
Breakpoint 1, main () at test.c:9
9 n += i;
(gdb) p i
$1 = 10
?
可以看到,該例中在第8行(也就是運(yùn)行完第7行的for循環(huán))設(shè)置了一個(gè)“i==0”的條件斷點(diǎn),在程序運(yùn)行之后可以看出,程序確實(shí)在i為10時(shí)暫停運(yùn)行。
3.Gdb中源碼查看相關(guān)命令
在Gdb中可以查看源碼以方便其他操作,它的常見相關(guān)命令如表3.12所示:
表3.12 Gdb源碼查看相關(guān)相關(guān)命令
| 命 令 格 式 | 含義 |
| list <行號(hào)>|<函數(shù)名> | 查看指定位置代碼 |
| file [文件名] | 加載指定文件 |
| forward-search 正則表達(dá)式 | 源代碼前向搜索 |
| reverse-search 正則表達(dá)式 | 源代碼后向搜索 |
| dir dir | 停止路徑名 |
| show directories | 顯示定義了的源文件搜索路徑 |
| info line | 顯示加載到Gdb內(nèi)存中的代碼 |
4.Gdb中查看運(yùn)行數(shù)據(jù)相關(guān)命令
Gdb中查看運(yùn)行數(shù)據(jù)是指當(dāng)程序處于“運(yùn)行”或“暫停”狀態(tài)時(shí),可以查看的變量及表達(dá)式的信息,其常見命令如表3.13所示:
表3.13 Gdb查看運(yùn)行數(shù)據(jù)相關(guān)命令
| 命 令 格 式 | 含義 |
| print 表達(dá)式|變量 | 查看程序運(yùn)行時(shí)對(duì)應(yīng)表達(dá)式和變量的值 |
| x <n/f/u> | 查看內(nèi)存變量?jī)?nèi)容。其中n為整數(shù)表示顯示內(nèi)存的長(zhǎng)度,f表示顯示的格式,u表示從當(dāng)前地址往后請(qǐng)求顯示的字節(jié)數(shù) |
| display 表達(dá)式 | 設(shè)定在單步運(yùn)行或其他情況中,自動(dòng)顯示的對(duì)應(yīng)表達(dá)式的內(nèi)容 |
5.Gdb中修改運(yùn)行參數(shù)相關(guān)命令
Gdb還可以修改運(yùn)行時(shí)的參數(shù),并使該變量按照用戶當(dāng)前輸入的值繼續(xù)運(yùn)行。它的設(shè)置方法為:在單步執(zhí)行的過程中,鍵入命令“set 變量=設(shè)定值”。這樣,在此之后,程序就會(huì)按照該設(shè)定的值運(yùn)行了。下面,筆者結(jié)合上一節(jié)的代碼將n的初始值設(shè)為4,其代碼如下所示:
?
(Gdb)?b 7
Breakpoint 5 at 0x804847a: file test.c, line 7.
(Gdb)?r
Starting program: /home/yul/test
The sum of 1-m is 1275
?
Breakpoint 5, main () at test.c:7
7 for(i=1; i<=50; i++)
(Gdb)?set n=4
(Gdb)?c
Continuing.
The sum of 1-50 is 1279
?
Program exited with code 031.
?
可以看到,最后的運(yùn)行結(jié)果確實(shí)比之前的值大了4。
?
| ? | Gdb的使用切記點(diǎn): · 在Gcc編譯選項(xiàng)中一定要加入”-g”。 · 只有在代碼處于“運(yùn)行”或“暫停”狀態(tài)時(shí)才能查看變量值。 · 設(shè)置斷點(diǎn)后程序在指定行之前停止。 |
Make工程管理器
到此為止,讀者已經(jīng)了解了如何在Linux下使用編輯器編寫代碼,如何使用Gcc把代碼編譯成可執(zhí)行文件,還學(xué)習(xí)了如何使用Gdb來調(diào)試程序,那么,所有的工作看似已經(jīng)完成了,為什么還需要Make這個(gè)工程管理器呢?
所謂工程管理器,顧名思義,是指管理較多的文件的。讀者可以試想一下,有一個(gè)上百個(gè)文件的代碼構(gòu)成的項(xiàng)目,如果其中只有一個(gè)或少數(shù)幾個(gè)文件進(jìn)行了修改,按照之前所學(xué)的Gcc編譯工具,就不得不把這所有的文件重新編譯一遍,因?yàn)榫幾g器并不知道哪些文件是最近更新的,而只知道需要包含這些文件才能把源代碼編譯成可執(zhí)行文件,于是,程序員就不能不再重新輸入數(shù)目如此龐大的文件名以完成最后的編譯工作。
但是,請(qǐng)讀者仔細(xì)回想一下本書在3.1.2節(jié)中所闡述的編譯過程,編譯過程是分為編譯、匯編、鏈接不同階段的,其中編譯階段僅檢查語(yǔ)法錯(cuò)誤以及函數(shù)與變量的聲明是否正確聲明了,在鏈接階段則主要完成是函數(shù)鏈接和全局變量的鏈接。因此,那些沒有改動(dòng)的源代碼根本不需要重新編譯,而只要把它們重新鏈接進(jìn)去就可以了。所以,人們就希望有一個(gè)工程管理器能夠自動(dòng)識(shí)別更新了的文件代碼,同時(shí)又不需要重復(fù)輸入冗長(zhǎng)的命令行,這樣,Make工程管理器也就應(yīng)運(yùn)而生了。
實(shí)際上,Make工程管理器也就是個(gè)“自動(dòng)編譯管理器”,這里的“自動(dòng)”是指它能夠根據(jù)文件時(shí)間戳自動(dòng)發(fā)現(xiàn)更新過的文件而減少編譯的工作量,同時(shí),它通過讀入Makefile文件的內(nèi)容來執(zhí)行大量的編譯工作。用戶只需編寫一次簡(jiǎn)單的編譯語(yǔ)句就可以了。它大大提高了實(shí)際項(xiàng)目的工作效率,而且?guī)缀跛蠰inux下的項(xiàng)目編程均會(huì)涉及到它,希望讀者能夠認(rèn)真學(xué)習(xí)本節(jié)內(nèi)容。
Makefile基本結(jié)構(gòu)
Makefile是Make讀入的惟一配置文件,因此本節(jié)的內(nèi)容實(shí)際就是講述Makefile的編寫規(guī)則。在一個(gè)Makefile中通常包含如下內(nèi)容:
· 需要由make工具創(chuàng)建的目標(biāo)體(target),通常是目標(biāo)文件或可執(zhí)行文件;
· 要?jiǎng)?chuàng)建的目標(biāo)體所依賴的文件(dependency_file);
· 創(chuàng)建每個(gè)目標(biāo)體時(shí)需要運(yùn)行的命令(command)。
它的格式為:
?
target: dependency_files
command
?
例如,有兩個(gè)文件分別為hello.c和hello.h,創(chuàng)建的目標(biāo)體為hello.o,執(zhí)行的命令為gcc編譯指令:gcc –c hello.c,那么,對(duì)應(yīng)的Makefile就可以寫為:
?
#The simplest example
hello.o: hello.c hello.h
gcc –c hello.c –o hello.o
?
接著就可以使用make了。使用make的格式為:make target,這樣make就會(huì)自動(dòng)讀入Makefile(也可以是首字母小寫makefile)并執(zhí)行對(duì)應(yīng)target的command語(yǔ)句,并會(huì)找到相應(yīng)的依賴文件。如下所示:
?
[root@localhost makefile]#?make hello.o
gcc –c hello.c –o hello.o
[root@localhost makefile]# ls
hello.c hello.h?hello.o?Makefile
?
可以看到,Makefile執(zhí)行了“hello.o”對(duì)應(yīng)的命令語(yǔ)句,并生成了“hello.o”目標(biāo)體。
?
| ? | 注意 | 在Makefile中的每一個(gè)command前必須有“Tab”符,否則在運(yùn)行make命令時(shí)會(huì)出錯(cuò)。 |
Makefile變量
上面示例的Makefile在實(shí)際中是幾乎不存在的,因?yàn)樗^于簡(jiǎn)單,僅包含兩個(gè)文件和一個(gè)命令,在這種情況下完全不必要編寫Makefile而只需在Shell中直接輸入即可,在實(shí)際中使用的Makefile往往是包含很多的文件和命令的,這也是Makefile產(chǎn)生的原因。下面就可給出稍微復(fù)雜一些的Makefile進(jìn)行講解:
?
sunq:kang.o yul.o
Gcc kang.o bar.o -o myprog
kang.o?: kang.c kang.h head.h
Gcc –Wall –O -g –c kang.c -o kang.o
yul.o?: bar.c head.h
Gcc - Wall –O -g –c yul.c -o yul.o
?
在這個(gè)Makefile中有三個(gè)目標(biāo)體(target),分別為sunq、kang.o和yul.o,其中第一個(gè)目標(biāo)體的依賴文件就是后兩個(gè)目標(biāo)體。如果用戶使用命令“make sunq”,則make管理器就是找到sunq目標(biāo)體開始執(zhí)行。
這時(shí),make會(huì)自動(dòng)檢查相關(guān)文件的時(shí)間戳。首先,在檢查“kang.o”、“yul.o”和“sunq”三個(gè)文件的時(shí)間戳之前,它會(huì)向下查找那些把“kang.o”或“yul.o”做為目標(biāo)文件的時(shí)間戳。比如,“kang.o”的依賴文件為:“kang.c”、“kang.h”、“head.h”。如果這些文件中任何一個(gè)的時(shí)間戳比“kang.o”新,則命令“gcc –Wall –O -g –c kang.c -o kang.o”將會(huì)執(zhí)行,從而更新文件“kang.o”。在更新完“kang.o”或“yul.o”之后,make會(huì)檢查最初的“kang.o”、“yul.o”和“sunq”三個(gè)文件,只要文件“kang.o”或“yul.o”中的任比文件時(shí)間戳比“sunq”新,則第二行命令就會(huì)被執(zhí)行。這樣,make就完成了自動(dòng)檢查時(shí)間戳的工作,開始執(zhí)行編譯工作。這也就是Make工作的基本流程。
接下來,為了進(jìn)一步簡(jiǎn)化編輯和維護(hù)Makefile,make允許在Makefile中創(chuàng)建和使用變量。變量是在Makefile中定義的名字,用來代替一個(gè)文本字符串,該文本字符串稱為該變量的值。在具體要求下,這些值可以代替目標(biāo)體、依賴文件、命令以及makefile文件中其它部分。在Makefile中的變量定義有兩種方式:一種是遞歸展開方式,另一種是簡(jiǎn)單方式。
遞歸展開方式定義的變量是在引用在該變量時(shí)進(jìn)行替換的,即如果該變量包含了對(duì)其他變量的應(yīng)用,則在引用該變量時(shí)一次性將內(nèi)嵌的變量全部展開,雖然這種類型的變量能夠很好地完成用戶的指令,但是它也有嚴(yán)重的缺點(diǎn),如不能在變量后追加內(nèi)容(因?yàn)檎Z(yǔ)句:CFLAGS = $(CFLAGS) -O在變量擴(kuò)展過程中可能導(dǎo)致無窮循環(huán))。
為了避免上述問題,簡(jiǎn)單擴(kuò)展型變量的值在定義處展開,并且只展開一次,因此它不包含任何對(duì)其它變量的引用,從而消除變量的嵌套引用。
遞歸展開方式的定義格式為:VAR=var
簡(jiǎn)單擴(kuò)展方式的定義格式為:VAR:=var
Make中的變量使用均使用格式為:$(VAR)
?
| ? | 注意 | 變量名是不包括“:”、“#”、“=”結(jié)尾空格的任何字符串。同時(shí),變量名中包含字母、數(shù)字以及下劃線以外的情況應(yīng)盡量避免,因?yàn)樗鼈兛赡茉趯肀毁x予特別的含義。 變量名是大小寫敏感的,例如變量名“foo”、“FOO”、和“Foo”代表不同的變量。 推薦在makefile內(nèi)部使用小寫字母作為變量名,預(yù)留大寫字母作為控制隱含規(guī)則參數(shù)或用戶重載命令選項(xiàng)參數(shù)的變量名。 |
?
下面給出了上例中用變量替換修改后的Makefile,這里用OBJS代替kang.o和yul.o,用CC代替Gcc,用CFLAGS代替“-Wall -O –g”。這樣在以后修改時(shí),就可以只修改變量定義,而不需要修改下面的定義實(shí)體,從而大大簡(jiǎn)化了Makefile維護(hù)的工作量。
經(jīng)變量替換后的Makefile如下所示:
?
OBJS = kang.o yul.o
CC = Gcc
CFLAGS = -Wall -O -g
sunq : $(OBJS)
$(CC) $(OBJS) -o sunq
kang.o : kang.c kang.h
$(CC) $(CFLAGS) -c kang.c -o kang.o
yul.o : yul.c yul.h
$(CC) $(CFLAGS) -c yul.c -o yul.o
?
可以看到,此處變量是以遞歸展開方式定義的。
Makefile中的變量分為用戶自定義變量、預(yù)定義變量、自動(dòng)變量及環(huán)境變量。如上例中的OBJS就是用戶自定義變量,自定義變量的值由用戶自行設(shè)定,而預(yù)定義變量和自動(dòng)變量為通常在Makefile都會(huì)出現(xiàn)的變量,其中部分有默認(rèn)值,也就是常見的設(shè)定值,當(dāng)然用戶可以對(duì)其進(jìn)行修改。
預(yù)定義變量包含了常見編譯器、匯編器的名稱及其編譯選項(xiàng)。下表3.14列出了Makefile中常見預(yù)定義變量及其部分默認(rèn)值。
表3.14 Makefile中常見預(yù)定義變量
| 命 令 格 式 | 含義 |
| AR | 庫(kù)文件維護(hù)程序的名稱,默認(rèn)值為ar |
| AS | 匯編程序的名稱,默認(rèn)值為as |
| CC | C編譯器的名稱,默認(rèn)值為cc |
| CPP | C預(yù)編譯器的名稱,默認(rèn)值為$(CC) –E |
| CXX | C++編譯器的名稱,默認(rèn)值為g++ |
| FC | FORTRAN編譯器的名稱,默認(rèn)值為f77 |
| RM | 文件刪除程序的名稱,默認(rèn)值為rm –f |
| ARFLAGS | 庫(kù)文件維護(hù)程序的選項(xiàng),無默認(rèn)值 |
| ASFLAGS | 匯編程序的選項(xiàng),無默認(rèn)值 |
| CFLAGS | C編譯器的選項(xiàng),無默認(rèn)值 |
| CPPFLAGS | C預(yù)編譯的選項(xiàng),無默認(rèn)值 |
| CXXFLAGS | C++編譯器的選項(xiàng),無默認(rèn)值 |
| FFLAGS | FORTRAN編譯器的選項(xiàng),無默認(rèn)值 |
?
可以看出,上例中的CC和CFLAGS是預(yù)定義變量,其中由于CC沒有采用默認(rèn)值,因此,需要把“CC=Gcc”明確列出來。
由于常見的Gcc編譯語(yǔ)句中通常包含了目標(biāo)文件和依賴文件,而這些文件在Makefile文件中目標(biāo)體的一行已經(jīng)有所體現(xiàn),因此,為了進(jìn)一步簡(jiǎn)化Makefile的編寫,就引入了自動(dòng)變量。自動(dòng)變量通常可以代表編譯語(yǔ)句中出現(xiàn)目標(biāo)文件和依賴文件等,并且具有本地含義(即下一語(yǔ)句中出現(xiàn)的相同變量代表的是下一語(yǔ)句的目標(biāo)文件和依賴文件)。下表3.15列出了Makefile中常見自動(dòng)變量。
表3.15Makefile中常見自動(dòng)變量
| 命令格式 | 含義 |
| $* | 不包含擴(kuò)展名的目標(biāo)文件名稱 |
| $+ | 所有的依賴文件,以空格分開,并以出現(xiàn)的先后為序,可能包含重復(fù)的依賴文件 |
| $< | 第一個(gè)依賴文件的名稱 |
| $? | 所有時(shí)間戳比目標(biāo)文件晚的依賴文件,并以空格分開 |
| 命令格式 | 含義 |
| $@ | 目標(biāo)文件的完整名稱 |
| $^ | 所有不重復(fù)的依賴文件,以空格分開 |
| $% | 如果目標(biāo)是歸檔成員,則該變量表示目標(biāo)的歸檔成員名稱 |
?
自動(dòng)變量的書寫比較難記,但是在熟練了之后會(huì)非常的方便,請(qǐng)讀者結(jié)合下例中的自動(dòng)變量改寫的Makefile進(jìn)行記憶。
?
OBJS = kang.o yul.o
CC = Gcc
CFLAGS = -Wall -O -g
sunq : $(OBJS)
$(CC)?$^?-o?$@
kang.o : kang.c kang.h
$(CC) $(CFLAGS) -c?$<?-o?$@
yul.o : yul.c yul.h
$(CC) $(CFLAGS) -c?$<?-o?$@
?
另外,在Makefile中還可以使用環(huán)境變量。使用環(huán)境變量的方法相對(duì)比較簡(jiǎn)單,make在啟動(dòng)時(shí)會(huì)自動(dòng)讀取系統(tǒng)當(dāng)前已經(jīng)定義了的環(huán)境變量,并且會(huì)創(chuàng)建與之具有相同名稱和數(shù)值的變量。但是,如果用戶在Makefile中定義了相同名稱的變量,那么用戶自定義變量將會(huì)覆蓋同名的環(huán)境變量。
Makefile規(guī)則
Makefile的規(guī)則是Make進(jìn)行處理的依據(jù),它包括了目標(biāo)體、依賴文件及其之間的命令語(yǔ)句。一般的,Makefile中的一條語(yǔ)句就是一個(gè)規(guī)則。在上面的例子中,都顯示地指出了Makefile中的規(guī)則關(guān)系,如“$(CC) $(CFLAGS) -c $< -o $@”,但為了簡(jiǎn)化Makefile的編寫,make還定義了隱式規(guī)則和模式規(guī)則,下面就分別對(duì)其進(jìn)行講解。
1.隱式規(guī)則
隱含規(guī)則能夠告訴make怎樣使用傳統(tǒng)的技術(shù)完成任務(wù),這樣,當(dāng)用戶使用它們時(shí)就不必詳細(xì)指定編譯的具體細(xì)節(jié),而只需把目標(biāo)文件列出即可。Make會(huì)自動(dòng)搜索隱式規(guī)則目錄來確定如何生成目標(biāo)文件。如上例就可以寫成:
?
OBJS = kang.o yul.o
CC = Gcc
CFLAGS = -Wall -O -g
sunq : $(OBJS)
$(CC) $^ -o $@
?
為什么可以省略后兩句呢?因?yàn)镸ake的隱式規(guī)則指出:所有“.o”文件都可自動(dòng)由“.c”文件使用命令“$(CC) $(CPPFLAGS) $(CFLAGS) -c file.c –o file.o”生成。這樣“kang.o”和“yul.o”就會(huì)分別調(diào)用“$(CC) $(CFLAGS) -c kang.c -o kang.o”和“$(CC) $(CFLAGS) -c yul.c -o yul.o”生成。
?
| ? | 注意 | 在隱式規(guī)則只能查找到相同文件名的不同后綴名文件,如”kang.o”文件必須由”kang.c”文件生成。 |
?
下表3.16給出了常見的隱式規(guī)則目錄:
表3.16 Makefile中常見隱式規(guī)則目錄
| 對(duì)應(yīng)語(yǔ)言后綴名 | 規(guī)則 |
| C編譯:.c變?yōu)?span style="margin:0px; padding:0px">.o | $(CC) –c $(CPPFLAGS) $(CFLAGS) |
| C++編譯:.cc或.C變?yōu)?span style="margin:0px; padding:0px">.o | $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) |
| Pascal編譯:.p變?yōu)?span style="margin:0px; padding:0px">.o | $(PC) -c $(PFLAGS) |
| Fortran編譯:.r變?yōu)?span style="margin:0px; padding:0px">-o | $(FC) -c $(FFLAGS) |
2.模式規(guī)則
模式規(guī)則是用來定義相同處理規(guī)則的多個(gè)文件的。它不同于隱式規(guī)則,隱式規(guī)則僅僅能夠用make默認(rèn)的變量來進(jìn)行操作,而模式規(guī)則還能引入用戶自定義變量,為多個(gè)文件建立相同的規(guī)則,從而簡(jiǎn)化Makefile的編寫。
模式規(guī)則的格式類似于普通規(guī)則,這個(gè)規(guī)則中的相關(guān)文件前必須用“%”標(biāo)明。使用模式規(guī)則修改后的Makefile的編寫如下:
?
OBJS = kang.o yul.o
CC = Gcc
CFLAGS = -Wall -O -g
sunq : $(OBJS)
$(CC)?$^?-o?$@
%.o : %.c
$(CC) $(CFLAGS) -c?$<?-o?$@
Make使用
使用make管理器非常簡(jiǎn)單,只需在make命令的后面鍵入目標(biāo)名即可建立指定的目標(biāo),如果直接運(yùn)行make,則建立Makefile中的第一個(gè)目標(biāo)。
此外make還有豐富的命令行選項(xiàng),可以完成各種不同的功能。下表3.17列出了常用的make命令行選項(xiàng)。
表3.17 make的命令行選項(xiàng)
| 命令格式 | 含 義 |
| -C dir | 讀入指定目錄下的Makefile |
| -f file | 讀入當(dāng)前目錄下的file文件作為Makefile |
| 命令格式 | 含 義 |
| -i | 忽略所有的命令執(zhí)行錯(cuò)誤 |
| -I dir | 指定被包含的Makefile所在目錄 |
| -n | 只打印要執(zhí)行的命令,但不執(zhí)行這些命令 |
| -p | 顯示make變量數(shù)據(jù)庫(kù)和隱含規(guī)則 |
| -s | 在執(zhí)行命令時(shí)不顯示命令 |
| -w | 如果make在執(zhí)行過程中改變目錄,則打印當(dāng)前目錄名 |
使用autotools
在上一小節(jié),讀者已經(jīng)了解到了make項(xiàng)目管理器的強(qiáng)大功能。的確,Makefile可以幫助make完成它的使命,但要承認(rèn)的是,編寫Makefile確實(shí)不是一件輕松的事,尤其對(duì)于一個(gè)較大的項(xiàng)目而言更是如此。那么,有沒有一種輕松的手段生成Makefile而同時(shí)又能讓用戶享受make的優(yōu)越性呢?本節(jié)要講的autotools系列工具正是為此而設(shè)的,它只需用戶輸入簡(jiǎn)單的目標(biāo)文件、依賴文件、文件目錄等就可以輕松地生成Makefile了,這無疑是廣大用戶的所希望的。另外,這些工具還可以完成系統(tǒng)配置信息的收集,從而可以方便地處理各種移植性的問題。也正是基于此,現(xiàn)在Linux上的軟件開發(fā)一般都用autotools來制作Makefile,讀者在后面的講述中就會(huì)了解到。
autotools使用流程
正如前面所言,autotools是系列工具,讀者首先要確認(rèn)系統(tǒng)是否裝了以下工具(可以用which命令進(jìn)行查看)。
· aclocal
· autoscan
· autoconf
· autoheader
· automake
使用autotools主要就是利用各個(gè)工具的腳本文件以生成最后的Makefile。其總體流程是這樣的:
· 使用aclocal生成一個(gè)“aclocal.m4”文件,該文件主要處理本地的宏定義;
· 改寫“configure.scan”文件,并將其重命名為“configure.in”,并使用autoconf文件生成configure文件。
接下來,筆者將通過一個(gè)簡(jiǎn)單的hello.c例子帶領(lǐng)讀者熟悉autotools生成makefile的過程,由于在這過程中有涉及到較多的腳本文件,為了更清楚地了解相互之間的關(guān)系,強(qiáng)烈建議讀者實(shí)際動(dòng)手操作以體會(huì)其整個(gè)過程。
1.autoscan
它會(huì)在給定目錄及其子目錄樹中檢查源文件,若沒有給出目錄,就在當(dāng)前目錄及其子目錄樹中進(jìn)行檢查。它會(huì)搜索源文件以尋找一般的移植性問題并創(chuàng)建一個(gè)文件“configure.scan”,該文件就是接下來autoconf要用到的“configure.in”原型。如下所示:
?
[root@localhost automake]#?autoscan
autom4te: configure.ac: no such file or directory
autoscan: /usr/bin/autom4te failed with exit status: 1
[root@localhost automake]# ls
autoscan.log?configure.scan?hello.c
?
如上所示,autoscan首先會(huì)嘗試去讀入“configure.ac”(同configure.in的配置文件)文件,此時(shí)還沒有創(chuàng)建該配置文件,于是它會(huì)自動(dòng)生成一個(gè)“configure.in”的原型文件“configure.scan”。
2.autoconf
configure.in是autoconf的腳本配置文件,它的原型文件“configure.scan”如下所示:
?
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.59)
#The next one is modified by sunq
#AC_INIT(FULL-PACKAGE-NAME,VERSION,BUG-REPORT-ADDRESS)
AC_INIT(hello,1.0)
# The next one is added by sunq
AM_INIT_AUTOMAKE(hello,1.0)
AC_CONFIG_SRCDIR([hello.c])
AC_CONFIG_HEADER([config.h])
# Checks for programs.
AC_PROG_CC
# Checks for libraries.
# Checks for header files.
# Checks for typedefs, structures, and compiler characteristics.
# Checks for library functions.
AC_CONFIG_FILES([Makefile])
AC_OUTPUT
?
下面對(duì)這個(gè)腳本文件進(jìn)行解釋:
· 以“#”號(hào)開始的行為注釋。
· AC_PREREQ宏聲明本文件要求的autoconf版本,如本例使用的版本2.59。
· AC_INIT宏用來定義軟件的名稱和版本等信息,在本例中省略了BUG-REPORT-ADDRESS,一般為作者的e-mail。
· AM_INIT_AUTOMAKE是筆者另加的,它是automake所必備的宏,也同前面一樣,PACKAGE是所要產(chǎn)生軟件套件的名稱,VERSION是版本編號(hào)。
· AC_CONFIG_SRCDIR宏用來偵測(cè)所指定的源碼文件是否存在,來確定源碼目錄的有
效性。在此處為當(dāng)前目錄下的hello.c。
· AC_CONFIG_HEADER宏用于生成config.h文件,以便autoheader使用。
· AC_CONFIG_FILES宏用于生成相應(yīng)的Makefile文件。
· 中間的注釋間可以添加分別用戶測(cè)試程序、測(cè)試函數(shù)庫(kù)、測(cè)試頭文件等宏定義。
接下來首先運(yùn)行aclocal,生成一個(gè)“aclocal.m4”文件,該文件主要處理本地的宏定義。如下所示:
?
[root@localhost automake]#?aclocal
?
再接著運(yùn)行autoconf,生成“configure”可執(zhí)行文件。如下所示:
?
[root@localhost automake]#?autoconf
[root@localhost automake]#?ls
aclocal.m4 autom4te.cache autoscan.log?configure?configure.in hello.c
3.autoheader
接著使用autoheader命令,它負(fù)責(zé)生成config.h.in文件。該工具通常會(huì)從“acconfig.h”文件中復(fù)制用戶附加的符號(hào)定義,因此此處沒有附加符號(hào)定義,所以不需要?jiǎng)?chuàng)建“acconfig.h”文件。如下所示:
?
[root@localhost automake]#?autoheader
4.automake
這一步是創(chuàng)建Makefile很重要的一步,automake要用的腳本配置文件是Makefile.am,用戶需要自己創(chuàng)建相應(yīng)的文件。之后,automake工具轉(zhuǎn)換成Makefile.in。在該例中,筆者創(chuàng)建的文件為Makefile.am如下所示:
?
AUTOMAKE_OPTIONS=foreign
bin_PROGRAMS= hello
hello_SOURCES= hello.c
?
下面對(duì)該腳本文件的對(duì)應(yīng)項(xiàng)進(jìn)行解釋。
· 其中的AUTOMAKE_OPTIONS為設(shè)置automake的選項(xiàng)。由于GNU(在第1章中已經(jīng)有所介紹)對(duì)自己發(fā)布的軟件有嚴(yán)格的規(guī)范,比如必須附帶許可證聲明文件COPYING等,否則automake執(zhí)行時(shí)會(huì)報(bào)錯(cuò)。automake提供了三種軟件等級(jí):foreign、gnu和gnits,讓用戶選擇采用,默認(rèn)等級(jí)為gnu。在本例使用foreign等級(jí),它只檢測(cè)必須的文件。
· bin_PROGRAMS定義要產(chǎn)生的執(zhí)行文件名。如果要產(chǎn)生多個(gè)執(zhí)行文件,每個(gè)文件名用空格隔開。
· hello_SOURCES定義“hello”這個(gè)執(zhí)行程序所需要的原始文件。如果”hello”這個(gè)程序是由多個(gè)原始文件所產(chǎn)生的,則必須把它所用到的所有原始文件都列出來,并用空格隔開。例如:若目標(biāo)體“hello”需要“hello.c”、“sunq.c”、“hello.h”三個(gè)依賴文件,則定義hello_SOURCES=hello.c sunq.c hello.h。要注意的是,如果要定義多個(gè)執(zhí)行文件,則對(duì)每個(gè)執(zhí)行程序都要定義相應(yīng)的file_SOURCES。
接下來可以使用automake對(duì)其生成“configure.in”文件,在這里使用選項(xiàng)“—adding-missing”可以讓automake自動(dòng)添加有一些必需的腳本文件。如下所示:
?
[root@localhost automake]#?automake --add-missing
configure.in: installing './install-sh'
configure.in: installing './missing'
Makefile.am: installing 'depcomp'
[root@localhost automake]#?ls
aclocal.m4 autoscan.log?configure.in?hello.c Makefile.am missing
autom4te.cache configure depcomp install-sh Makefile.in config.h.in
?
可以看到,在automake之后就可以生成configure.in文件。
5.運(yùn)行configure
在這一步中,通過運(yùn)行自動(dòng)配置設(shè)置文件configure,把Makefile.in變成了最終的Makefile。如下所示:
?
[root@localhost automake]#?./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build enVironment is sane... yes
checking for gawk... gawk
checking whether make sets $(MAKE)... yes
checking for Gcc... Gcc
checking for C compiler default output file name... a.out
checking whether the C compiler works... yes
checking whether we are cross compiling... no
checking for suffix of executables...
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether Gcc accepts -g... yes
checking for Gcc option to accept ANSI C... none needed
checking for style of include used by make... GNU
checking dependency style of Gcc... Gcc3
configure: creating ./config.status
config.status:?creating Makefile
config.status: executing depfiles commands
可以看到,在運(yùn)行configure時(shí)收集了系統(tǒng)的信息,用戶可以在configure命令中對(duì)其進(jìn)行方便地配置。在./configure的自定義參數(shù)有兩種,一種是開關(guān)式(--enable-XXX或--disable-XXX),另一種是開放式,即后面要填入一串字符(--with-XXX=yyyy)參數(shù)。讀者可以自行嘗試其使用方法。另外,讀者可以查看同一目錄下的”config.log”文件,以方便調(diào)試之用。
到此為止,makefile就可以自動(dòng)生成了。回憶整個(gè)步驟,用戶不再需要定制不同的規(guī)則,而只需要輸入簡(jiǎn)單的文件及目錄名即可,這樣就大大方便了用戶的使用。下面的圖3.9總結(jié)了上述過程:
?
圖3.9 autotools生成Makefile流程圖
使用autotools所生成的Makefile
autotools生成的Makefile除具有普通的編譯功能外,還具有以下主要功能(感興趣的讀者可以查看這個(gè)簡(jiǎn)單的hello.c程序的makefile):
1.make
鍵入make默認(rèn)執(zhí)行”make all”命令,即目標(biāo)體為all,其執(zhí)行情況如下所示:
?
[root@localhost automake]#?make
if Gcc -DPACKAGE_NAME="" -DPACKAGE_TARNAME="" -DPACKAGE_VERSION="" -DPACKAGE_STRING="" -DPACKAGE_BUGREPORT="" -DPACKAGE="hello" -DVERSION="1.0" -I. -I. -g -O2 -MT hello.o -MD -MP -MF ".deps/hello.Tpo" -c -o hello.o hello.c;
then mv -f ".deps/hello.Tpo" ".deps/hello.Po"; else rm -f ".deps/hello.Tpo"; exit 1; fi
Gcc -g -O2 -o hello hello.o
此時(shí)在本目錄下就生成了可執(zhí)行文件“hello”,運(yùn)行“./hello”能出現(xiàn)正常結(jié)果,如下所示:
?
[root@localhost automake]#?./hello
Hello!Autoconf!
2.make install
此時(shí),會(huì)把該程序安裝到系統(tǒng)目錄中去,如下所示:
?
[root@localhost automake]#?make install
if Gcc -DPACKAGE_NAME="" -DPACKAGE_TARNAME="" -DPACKAGE_VERSION="" -DPACKAGE_STRING="" -DPACKAGE_BUGREPORT="" -DPACKAGE="hello" -DVERSION="1.0" -I. -I. -g -O2 -MT hello.o -MD -MP -MF ".deps/hello.Tpo" -c -o hello.o hello.c;
then mv -f ".deps/hello.Tpo" ".deps/hello.Po"; else rm -f ".deps/hello.Tpo"; exit 1; fi
Gcc -g -O2 -o hello hello.o
make[1]: Entering directory '/root/workplace/automake'
test -z "/usr/local/bin" || mkdir -p -- "/usr/local/bin"
/usr/bin/install -c 'hello' '/usr/local/bin/hello'
make[1]: Nothing to be done for 'install-data-am'.
make[1]: LeaVing directory '/root/workplace/automake'
?
此時(shí),若直接運(yùn)行hello,也能出現(xiàn)正確結(jié)果,如下所示:
?
[root@localhost automake]#?hello
Hello!Autoconf!
3.make clean
此時(shí),make會(huì)清除之前所編譯的可執(zhí)行文件及目標(biāo)文件(object file, *.o),如下所示:
?
[root@localhost automake]#?make clean
test -z "hello" || rm -f hello
rm -f *.o
4.make dist
此時(shí),make將程序和相關(guān)的文檔打包為一個(gè)壓縮文檔以供發(fā)布,如下所示:
?
[root@localhost automake]#?make dist
[root@localhost automake]# ls hello-1.0-tar.gz
hello-1.0-tar.gz
?
可見該命令生成了一個(gè)hello-1.0-tar.gz的壓縮文件。
由上面的講述讀者不難看出,autotools確實(shí)是軟件維護(hù)與發(fā)布的必備工具,也鑒于此,如今GUN的軟件一般都是由automake來制作的。
?
| ? | 想一想 | 對(duì)于automake制作的這類軟件,應(yīng)如何安裝呢? |
Vi使用練習(xí)
1.實(shí)驗(yàn)?zāi)康?/p>
通過指定指令的Vi操作練習(xí),使讀者能夠熟練使用Vi中的常見操作,并且熟悉Vi的三種模式,如果讀者能夠熟練掌握實(shí)驗(yàn)內(nèi)容中所要求的內(nèi)容,則表明對(duì)Vi的操作已經(jīng)很熟練了。
2.實(shí)驗(yàn)內(nèi)容
(1)在“/root”目錄下建一個(gè)名為“/Vi”的目錄。
(2)進(jìn)入“/Vi”目錄。
(3)將文件“/etc/inittab”復(fù)制到“/Vi”目錄下。
(4)使用Vi打開“/Vi”目錄下的inittab。
(5)設(shè)定行號(hào),指出設(shè)定initdefault(類似于“id:5:initdefault”)的所在行號(hào)。
(6)將光標(biāo)移到該行。
(7)復(fù)制該行內(nèi)容。
(8)將光標(biāo)移到最后一行行首。
(9)粘貼復(fù)制行的內(nèi)容。
(10)撤銷第9步的動(dòng)作。
(11)將光標(biāo)移動(dòng)到最后一行的行尾。
(12)粘貼復(fù)制行的內(nèi)容。
(13)光標(biāo)移到“si::sysinit:/etc/rc.d/rc.sysinit”。
(14)刪除該行。
(15)存盤但不退出。
(16)將光標(biāo)移到首行。
(17)插入模式下輸入“Hello,this is Vi world!”。
(18)返回命令行模式。
(19)向下查找字符串“0:wait”。
(20)再向上查找字符串“halt”。
(21)強(qiáng)制退出Vi,不存盤。
分別指出每個(gè)命令處于何種模式下?
3.實(shí)驗(yàn)步驟
(1)mkdir /root/Vi
(2)cd /root/Vi
(3)cp /etc/inittab ./
(4)Vi ./inittab
(5):set nu(底行模式)
(6)17<enter>(命令行模式)
(7)yy
(8)G
(9)p
(10)u
(11)$
(12)p
(13)21G
(14)dd
(15):w(底行模式)
(16)1G
(17)i 并輸入“Hello,this is Vi world!”(插入模式)
(18)Esc
(19)/0:wait(命令行模式)
(20)?halt
(21):q!(底行模式)
4.實(shí)驗(yàn)結(jié)果
該實(shí)驗(yàn)最后的結(jié)果只對(duì)“/root/inittab”增加了一行復(fù)制的內(nèi)容:“id:5:initdefault”。
用Gdb調(diào)試有問題的程序
1.實(shí)驗(yàn)?zāi)康?/p>
通過調(diào)試一個(gè)有問題的程序,使讀者進(jìn)一步熟練使用Vi操作,而且熟練掌握Gcc編譯命令及Gdb的調(diào)試命令,通過對(duì)有問題程序的跟蹤調(diào)試,進(jìn)一步提高發(fā)現(xiàn)問題和解決問題的能力。這是一個(gè)很小的程序,只有35行,希望讀者認(rèn)真調(diào)試。
2.實(shí)驗(yàn)內(nèi)容
(1)使用Vi編輯器,將以下代碼輸入到名為greet.c的文件中。此代碼的原意為輸出倒序main函數(shù)中定義的字符串,但結(jié)果顯示沒有輸出。代碼如下所示:
?
#include <stdio.h>
int display1(char *string);
int display2(char *string);
?
int main ()
{
char string[] = "Embedded Linux";
display1 (string);
display2 (string);
}
int display1 (char *string)
{
printf ("The original string is %s n", string);
}
int display2 (char *string1)
{
char *string2;
int size,i;
size = strlen (string1);
string2 = (char *) malloc (size + 1);
for (i = 0; i < size; i++)
string2[size - i] = string1[i];
string2[size+1] = ' ';
printf("The string afterward is %sn",string2);
}
?
(2)使用Gcc編譯這段代碼,注意要加上“-g”選項(xiàng)以方便之后的調(diào)試。
(3)運(yùn)行生成的可執(zhí)行文件,觀察運(yùn)行結(jié)果。
(4)使用Gdb調(diào)試程序,通過設(shè)置斷點(diǎn)、單步跟蹤,一步步找出錯(cuò)誤所在。
(5)糾正錯(cuò)誤,更改源程序并得到正確的結(jié)果。
3.實(shí)驗(yàn)步驟
(1)在工作目錄上新建文件greet.c,并用Vi啟動(dòng):vi greet.c。
(2)在Vi中輸入以上代碼。
(3)在Vi中保存并退出:wq。
(4)用Gcc編譯:gcc -g greet.c -o greet。
(5)運(yùn)行g(shù)reet:./greet,輸出為:
?
The original string is Embedded Linux
The string afterward is
?
可見,該程序沒有能夠倒序輸出。
(6)啟動(dòng)Gdb調(diào)試:gdb greet。
(7)查看源代碼,使用命令“l(fā)”。
(8)在30行(for循環(huán)處)設(shè)置斷點(diǎn),使用命令“b 30”。
(9)在33行(printf函數(shù)處)設(shè)置斷點(diǎn),使用命令“b 33”。
(10)查看斷點(diǎn)設(shè)置情況,使用命令“info b”。
(11)運(yùn)行代碼,使用命令“r”。
(12)單步運(yùn)行代碼,使用命令“n”。
(13)查看暫停點(diǎn)變量值,使用命令“p string2[size - i]”。
(14)繼續(xù)單步運(yùn)行代碼數(shù)次,并使用命令查看,發(fā)現(xiàn)string2[size-1]的值正確。
(15)繼續(xù)程序的運(yùn)行,使用命令“c”。
(16)程序在printf前停止運(yùn)行,此時(shí)依次查看string2[0]、string2[1]…,發(fā)現(xiàn)string[0]沒有被正確賦值,而后面的復(fù)制都是正確的,這時(shí),定位程序第31行,發(fā)現(xiàn)程序運(yùn)行結(jié)果錯(cuò)誤的原因在于“size-1”。由于i只能增到“size-1”,這樣string2[0]就永遠(yuǎn)不能被賦值而保持NULL,故輸不出任何結(jié)果。
(17)退出Gdb,使用命令q。
(18)重新編輯greet.c,把其中的“string2[size - i] = string1[i]”改為“string2[size – i - 1] = string1[i];”即可。
(19)使用Gcc重新編譯:gcc -g greet.c -o greet。
(20)查看運(yùn)行結(jié)果:./greet
?
The original string is Embedded Linux
The string afterward is xuniL deddedbmE
?
這時(shí),輸入結(jié)果正確。
4.實(shí)驗(yàn)結(jié)果
將原來有錯(cuò)的程序經(jīng)過Gdb調(diào)試,找出問題所在,并修改源代碼,輸出正確的倒序顯示字符串的結(jié)果。
編寫包含多文件的Makefile
1.實(shí)驗(yàn)?zāi)康?/p>
通過對(duì)包含多文件的Makefile的編寫,熟悉各種形式的Makefile,并且進(jìn)一步加深對(duì)Makefile中用戶自定義變量、自動(dòng)變量及預(yù)定義變量的理解。
2.實(shí)驗(yàn)過程
(1)用Vi在同一目錄下編輯兩個(gè)簡(jiǎn)單的Hello程序,如下所示:
?
#hello.c
#include "hello.h"
int main()
{
printf("Hello everyone!n");
}
#hello.h
#include <stdio.h>
?
(2)仍在同一目錄下用Vi編輯Makefile,且不使用變量替換,用一個(gè)目標(biāo)體實(shí)現(xiàn)(即直接將hello.c和hello.h編譯成hello目標(biāo)體)。然后用make驗(yàn)證所編寫的Makefile是否正確。
(3)將上述Makefile使用變量替換實(shí)現(xiàn)。同樣用make驗(yàn)證所編寫的Makefile是否正確
(4)用編輯另一Makefile,取名為Makefile1,不使用變量替換,但用兩個(gè)目標(biāo)體實(shí)現(xiàn)(也就是首先將hello.c和hello.h編譯為hello.o,再將hello.o編譯為hello),再用make的”-f”選項(xiàng)驗(yàn)證這個(gè)Makefile1的正確性。
(5)將上述Makefile1使用變量替換實(shí)現(xiàn)。
3.實(shí)驗(yàn)步驟
(1)用Vi打開上述兩個(gè)代碼文件“hello.c”和“hello.h”。
(2)在shell命令行中用Gcc嘗試編譯,使用命令:”Gcc hello.c –o hello”,并運(yùn)行hello可執(zhí)行文件查看結(jié)果。
(3)刪除此次編譯的可執(zhí)行文件:rm hello。
(4)用Vi編輯Makefile,如下所示:
?
hello:hello.c hello.h
Gcc hello.c -o hello
?
(5)退出保存,在shell中鍵入:make,查看結(jié)果。
(6)再次用Vi打開Makefile,用變量進(jìn)行替換,如下所示:
?
OBJS :=hello.o
CC :=Gcc
hello:$(OBJS)
$(CC) $^ -o $@
?
(7)退出保存,在shell中鍵入:make,查看結(jié)果。
(8)用Vi編輯Makefile1,如下所示:
?
hello:hello.o
Gcc hello.o -o hello
hello.o:hello.c hello.h
Gcc -c hello.c -o hello.o
?
(9)退出保存,在shell中鍵入:make -f Makefile1,查看結(jié)果。
(10)再次用Vi編輯Makefile1,如下所示:
?
OBJS1 :=hello.o
OBJS2 :=hello.c hello.h
CC :=Gcc
hello:$(OBJS1)
$(CC) $^ -o $@
$(OBJS1):$(OBJS2)
$(CC) -c $< -o $@
?
在這里請(qǐng)注意區(qū)別“$^”和“$<”。
(11)退出保存,在shell中鍵入:make -f Makefile1,查看結(jié)果
4.實(shí)驗(yàn)結(jié)果
各種不同形式的makefile都能完成其正確的功能。
使用autotools生成包含多文件的Makefile
1.實(shí)驗(yàn)?zāi)康?/p>
通過使用autotools生成包含多文件的Makefile,進(jìn)一步掌握autotools的正確使用方法。同時(shí),掌握Linux下安裝軟件的常用方法。
2.實(shí)驗(yàn)過程
(1)在原目錄下新建文件夾auto。
(2)利用上例的兩個(gè)代碼文件“hello.c”和“hello.h”,并將它們復(fù)制到該目錄下。
(3)使用autoscan生成configure.scan。
(4)編輯configure.scan,修改相關(guān)內(nèi)容,并將其重命名為configure.in。
(5)使用aclocal生成aclocal.m4。
(6)使用autoconf生成configure。
(7)使用autoheader生成config.in.h。
(8)編輯Makefile.am。
(9)使用automake生成Makefile.in。
(10)使用configure生成Makefile。
(11)使用make生成hello可執(zhí)行文件,并在當(dāng)前目錄下運(yùn)行hello查看結(jié)果。
(12)使用make install將hello安裝到系統(tǒng)目錄下,并運(yùn)行,查看結(jié)果。
(13)使用make dist生成hello壓縮包。
(14)解壓hello壓縮包。
(15)進(jìn)入解壓目錄。
(16)在該目錄下安裝hello軟件。
3.實(shí)驗(yàn)步驟
(1)mkdir ./auto。
(2)cp hello.* ./auto(假定原先在“hello.c”文件目錄下)。
(3)命令:autoscan。
(4)使用Vi編輯configure.scan為:
?
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
?
AC_PREREQ(2.59)
AC_INIT(hello, 1.0)
AM_INIT_AUTOMAKE(hello,1.0)
AC_CONFIG_SRCDIR([hello.h])
AC_CONFIG_HEADER([config.h])
# Checks for programs.
AC_PROG_CC
# Checks for libraries.
# Checks for header files.
# Checks for typedefs, structures, and compiler characteristics.
# Checks for library functions.
AC_OUTPUT(Makefile)
?
(5)保存退出,并重命名為configure.in。
(6)運(yùn)行:aclocal。
(7)運(yùn)行:autoconf,并用ls查看是否生成了configure可執(zhí)行文件。
(8)運(yùn)行:autoheader。
(9)用Vi編輯Makefile.am文件為:
?
AUTOMAKE_OPTIONS=foreign
bin_PROGRAMS=hello
hello_SOURCES=hello.c hello.h
?
(10)運(yùn)行:automake。
(11)運(yùn)行:./configure。
(12)運(yùn)行:make。
(13)運(yùn)行:./hello,查看結(jié)果是否正確。
(14)運(yùn)行:make install。
(15)運(yùn)行:hello,查看結(jié)果是否正確。
(16)運(yùn)行:make dist。
(17)在當(dāng)前目錄下解壓hello-1.0.tar.gz:tar –zxvf hello-1.0.tar.gz。
(18)進(jìn)入解壓目錄:cd ./hello-1.0。
(19)下面開始Linux下常見的安裝軟件步驟:./configure。
(20)運(yùn)行:make。
(21)運(yùn)行:./hello(在正常安裝時(shí)這一步可省略)。
(22)運(yùn)行:make install。
(23)運(yùn)行:hello,查看結(jié)果是否正確。
4.實(shí)驗(yàn)結(jié)果
能夠正確使用autotools生成Makefile,并且能夠安裝成功短小的Hello軟件。
總結(jié)
以上是生活随笔為你收集整理的gcc选项 和 gdb 使用的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Adobe illustrator 调整
- 下一篇: 生信分析和统计绘图资源推荐!