GCC命令詳解

樹下聽雨發表於2020-10-10

GNU CC(簡稱為Gcc)是GNU專案中符合ANSI C標準的編譯系統,能夠編譯用C、C++和Object C等語言編寫的程式。Gcc不僅功能強大,而且可以編譯如C、C++、Object C、Java、Fortran、Pascal、Modula-3和Ada等多種語言,而且Gcc又是一個交叉平臺編譯器,它能夠在當前CPU平臺上為多種不同體系結構的硬體平臺開發軟體,因此尤其適合在嵌入式領域的開發編譯。本章中的示例,除非特別註明,否則均採用Gcc版本為4.0.0。

 

 

GCC入門基礎

表3.6 Gcc所支援字尾名解釋

後 綴 名

所對應的語言

後 綴 名

所對應的語言

.c

C原始程式

.s/.S

組合語言原始程式

.C/.cc/.cxx

C++原始程式

.h

預處理檔案(標頭檔案)

.m

Objective-C原始程式

.o

目標檔案

.i

已經過預處理的C原始程式

.a/.so

編譯後的庫檔案

.ii

已經過預處理的C++原始程式

   

如本章開頭提到的,Gcc的編譯流程分為了四個步驟,分別為:

· 預處理(Pre-Processing)

· 編譯(Compiling)

· 彙編(Assembling)

· 連結(Linking)

下面就具體來檢視一下Gcc是如何完成四個步驟的。

首先,有以下hello.c原始碼

#include<stdio.h>

int main()

{

printf("Hello! This is our embedded world!n");

return 0;

}

(1)預處理階段

在該階段,編譯器將上述程式碼中的stdio.h編譯進來,並且使用者可以使用Gcc的選項”-E”進行檢視,該選項的作用是讓Gcc在預處理結束後停止編譯過程。

 

注意

Gcc指令的一般格式為:Gcc [選項] 要編譯的檔案 [選項] [目標檔案]

其中,目標檔案可預設,Gcc預設生成可執行的檔案,命為:編譯檔案.out

 

[root@localhost Gcc]# Gcc –E hello.c –o hello.i

 

在此處,選項”-o”是指目標檔案,由表3.6可知,”.i”檔案為已經過預處理的C原始程式。以下列出了hello.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確實進行了預處理,它把”stdio.h”的內容插入到hello.i檔案中。

(2)編譯階段

接下來進行的是編譯階段,在這個階段中,Gcc首先要檢查程式碼的規範性、是否有語法錯誤等,以確定程式碼的實際要做的工作,在檢查無誤後,Gcc把程式碼翻譯成組合語言。使用者可以使用”-S”選項來進行檢視,該選項只進行編譯而不進行彙編,生成彙編程式碼。

 

[root@localhost Gcc]# Gcc –S hello.i –o hello.s

 

以下列出了hello.s的內容,可見Gcc已經將其轉化為彙編了,感興趣的讀者可以分析一下這一行簡單的C語言小程式是如何用匯編程式碼實現的。

 

.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”檔案轉成目標檔案,讀者在此可使用選項”-c”就可看到彙編程式碼已轉化為”.o”的二進位制目的碼了。如下所示:

 

[root@localhost Gcc]# Gcc –c hello.s –o hello.o

 

(4)連結階段

在成功編譯之後,就進入了連結階段。在這裡涉及到一個重要的概念:函式庫。

讀者可以重新檢視這個小程式,在這個程式中並沒有定義”printf”的函式實現,且在預編譯中包含進的”stdio.h”中也只有該函式的宣告,而沒有定義函式的實現,那麼,是在哪裡實現”printf”函式的呢?最後的答案是:系統把這些函式實現都被做到名為libc.so.6的庫檔案中去了,在沒有特別指定時,Gcc會到系統預設的搜尋路徑”/usr/lib”下進行查詢,也就是連結到libc.so.6庫函式中去,這樣就能實現函式”printf”了,而這也就是連結的作用。

函式庫一般分為靜態庫和動態庫兩種。靜態庫是指編譯連結時,把庫檔案的程式碼全部加入到可執行檔案中,因此生成的檔案比較大,但在執行時也就不再需要庫檔案了。其字尾名一般為”.a”。動態庫與之相反,在編譯連結時並沒有把庫檔案的程式碼加入到可執行檔案中,而是在程式執行時由執行時連結檔案載入庫,這樣可以節省系統的開銷。動態庫一般字尾名為”.so”,如前面所述的libc.so.6就是動態庫。Gcc在編譯時預設使用動態庫。

完成了連結之後,Gcc就可以生成可執行檔案,如下所示。

 

[root@localhost Gcc]# Gcc hello.o –o hello

 

執行該可執行檔案,出現正確的結果如下。

 

[root@localhost Gcc]# ./hello

Hello! This is our embedded world!

Gcc編譯選項分析

Gcc有超過100個的可用選項,主要包括總體選項、告警和出錯選項、優化選項和體系結構相關選項。以下對每一類中最常用的選項進行講解。

(1)總體選項

Gcc的總結選項如表3.7所示,很多在前面的示例中已經有所涉及。

表3.7 Gcc總體選項列表

字尾名

所對應的語言

-c

只是編譯不連結,生成目標檔案“.o”

-S

只是編譯不彙編,生成彙編程式碼

-E

只進行預編譯,不做其他處理

-g

在可執行程式中包含標準除錯資訊

-o file

把輸出檔案輸出到file裡

-v

列印出編譯器內部編譯各過程的命令列資訊和編譯器的版本

-I dir

在標頭檔案的搜尋路徑列表中新增dir目錄

-L dir

在庫檔案的搜尋路徑列表中新增dir目錄

-static

連結靜態庫

-llibrary

連線名為library的庫檔案

 

對於“-c”、“-E”、“-o”、“-S”選項在前一小節中已經講解了其使用方法,在此主要講解另外兩個非常常用的庫依賴選項“-I dir”和“-L dir”。

· “-I dir”

正如上表中所述,“-I dir”選項可以在標頭檔案的搜尋路徑列表中新增dir目錄。由於Linux中標頭檔案都預設放到了“/usr/include/”目錄下,因此,當使用者希望新增放置在其他位置的標頭檔案時,就可以通過“-I dir”選項來指定,這樣,Gcc就會到相應的位置查詢對應的目錄。

比如在“/root/workplace/Gcc”下有兩個檔案:

 

 

#include<my.h>

int main()

{

printf(“Hello!!n”);

return 0;

}

 

#include<stdio.h>

 

這樣,就可在Gcc命令列中加入“-I”選項:

 

[root@localhost Gcc] Gcc hello1.c –I /root/workplace/Gcc/ -o hello1

 

這樣,Gcc就能夠執行出正確結果。

 

小知識

在include語句中,“<>”表示在標準路徑中搜尋標頭檔案,““””表示在本目錄中搜尋。故在上例中,可把hello1.c的“#include<my.h>”改為“#include “my.h””,就不需要加上“-I”選項了。

 

· “-L dir”

選項“-L dir”的功能與“-I dir”類似,能夠在庫檔案的搜尋路徑列表中新增dir目錄。例如有程式hello_sq.c需要用到目錄“/root/workplace/Gcc/lib”下的一個動態庫libsunq.so,則只需鍵入如下命令即可:

 

[root@localhost Gcc] Gcc hello_sq.c –L /root/workplace/Gcc/lib –lsunq –o hello_sq

 

需要注意的是,“-I dir”和“-L dir”都只是指定了路徑,而沒有指定檔案,因此不能在路徑中包含檔名。

另外值得詳細解釋一下的是“-l”選項,它指示Gcc去連線庫檔案libsunq.so。由於在Linux下的庫檔案命名時有一個規定:必須以lib三個字母開頭。因此在用-l選項指定連結的庫檔名時可以省去lib三個字母。也就是說Gcc在對”-lsunq”進行處理時,會自動去連結名為libsunq.so的檔案。

(2)告警和出錯選項

Gcc的告警和出錯選項如表3.8所示。

表3.8 Gcc總體選項列表

選項

含義

-ansi

支援符合ANSI標準的C程式

-pedantic

允許發出ANSI C標準所列的全部警告資訊

選項

含義

-pedantic-error

允許發出ANSI C標準所列的全部錯誤資訊

-w

關閉所有告警

-Wall

允許發出Gcc提供的所有有用的報警資訊

-werror

把所有的告警資訊轉化為錯誤資訊,並在告警發生時終止編譯過程

 

下面結合例項對這幾個告警和出錯選項進行簡單的講解。

如有以下程式段:

 

#include<stdio.h>

 

void main()

{

long long tmp = 1;

printf(“This is a bad code!n”);

return 0;

}

 

這是一個很糟糕的程式,讀者可以考慮一下有哪些問題?

· “-ansi”

該選項強制Gcc生成標準語法所要求的告警資訊,儘管這還並不能保證所有沒有警告的程式都是符合ANSI C標準的。執行結果如下所示:

 

[root@localhost Gcc]# Gcc –ansi warning.c –o warning

warning.c: 在函式“main”中:

warning.c:7 警告:在無返回值的函式中,“return”帶返回值

warning.c:4 警告:“main”的返回型別不是“int”

 

可以看出,該選項並沒有發現”long long”這個無效資料型別的錯誤。

· “-pedantic”

允許發出ANSI C標準所列的全部警告資訊,同樣也保證所有沒有警告的程式都是符合ANSI C標準的。其執行結果如下所示:

 

[root@localhost Gcc]# Gcc –pedantic warning.c –o warning

warning.c: 在函式“main”中:

warning.c:5 警告:ISO C90不支援“long long”

warning.c:7 警告:在無返回值的函式中,“return”帶返回值

warning.c:4 警告:“main”的返回型別不是“int”

 

可以看出,使用該選項檢視出了”long long”這個無效資料型別的錯誤。

· “-Wall”

允許發出Gcc能夠提供的所有有用的報警資訊。該選項的執行結果如下所示:

[root@localhost Gcc]# Gcc –Wall warning.c –o warning

warning.c:4 警告:“main”的返回型別不是“int”

warning.c: 在函式”main”中:

warning.c:7 警告:在無返回值的函式中,”return”帶返回值

warning.c:5 警告:未使用的變數“tmp”

 

使用“-Wall”選項找出了未使用的變數tmp,但它並沒有找出無效資料型別的錯誤。

另外,Gcc還可以利用選項對單獨的常見錯誤分別指定警告,有關具體選項的含義感興趣的讀者可以檢視Gcc手冊進行學習。

(3)優化選項

Gcc可以對程式碼進行優化,它通過編譯選項“-On”來控制優化程式碼的生成,其中n是一個代表優化級別的整數。對於不同版本的Gcc來講,n的取值範圍及其對應的優化效果可能並不完全相同,比較典型的範圍是從0變化到2或3。

不同的優化級別對應不同的優化處理工作。如使用優化選項“-O”主要進行執行緒跳轉(Thread Jump)和延遲退棧(Deferred Stack Pops)兩種優化。使用優化選項“-O2”除了完成所有“-O1”級別的優化之外,同時還要進行一些額外的調整工作,如處理器指令排程等。選項“-O3”則還包括迴圈展開和其他一些與處理器特性相關的優化工作。

雖然優化選項可以加速程式碼的執行速度,但對於除錯而言將是一個很大的挑戰。因為程式碼在經過優化之後,原先在源程式中宣告和使用的變數很可能不再使用,控制流也可能會突然跳轉到意外的地方,迴圈語句也有可能因為迴圈展開而變得到處都有,所有這些對除錯來講都將是一場噩夢。所以筆者建議在除錯的時候最好不使用任何優化選項,只有當程式在最終發行的時候才考慮對其進行優化。

(4)體系結構相關選項

Gcc的體系結構相關選項如表3.9所示。

表3.9Gcc體系結構相關選項列表

選項

含義

-mcpu=type

針對不同的CPU使用相應的CPU指令。可選擇的type有i386、i486、pentium及i686等

-mieee-fp

使用IEEE標準進行浮點數的比較

-mno-ieee-fp

不使用IEEE標準進行浮點數的比較

-msoft-float

輸出包含浮點庫呼叫的目的碼

-mshort

把int型別作為16位處理,相當於short int

-mrtd

強行將函式引數個數固定的函式用ret NUM返回,節省呼叫函式的一條指令

 

這些體系結構相關選項在嵌入式的設計中會有較多的應用,讀者需根據不同體系結構將對應的選項進行組合處理。在本書後面涉及到具體例項會有針對性的講解。

Gdb偵錯程式

除錯是所有程式設計師都會面臨的問題。如何提高程式設計師的除錯效率,更好更快地定位程式中的問題從而加快程式開發的進度,是大家共同面對的。就如讀者熟知的Windows下的一些除錯工具,如VC自帶的如設定斷點、單步跟蹤等,都受到了廣大使用者的讚賞。那麼,在Linux下有什麼很好的除錯工具呢?

本文所介紹的Gdb偵錯程式是一款GNU開發組織併發布的UNIX/Linux下的程式除錯工具。雖然,它沒有圖形化的友好介面,但是它強大的功能也足以與微軟的VC工具等媲美。下面就請跟隨筆者一步步學習Gdb偵錯程式。

Gdb使用流程

首先,筆者給出了一個短小的程式,由此帶領讀者熟悉一下Gdb的使用流程。強烈建議讀者能夠實際動手操作。

首先,開啟Linux下的編輯器Vi或者Emacs,編輯如下程式碼。(由於為了更好地熟悉Gdb的操作,筆者在此使用Vi編輯,希望讀者能夠參見3.3節中對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對test.c進行編譯,注意一定要加上選項”-g”,這樣編譯出的可執行程式碼中才包含除錯資訊,否則之後Gdb無法載入該可執行檔案。

 

[root@localhost Gdb]# gcc -g test.c -o test

 

雖然這段程式沒有錯誤,但除錯完全正確的程式可以更加了解Gdb的使用流程。接下來就啟動Gdb進行除錯。注意,Gdb進行除錯的是可執行檔案,而不是如”.c”的原始碼,因此,需要先通過Gcc編譯生成可執行檔案才能用Gdb進行除錯。

 

[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的啟動畫面中指出了Gdb的版本號、使用的庫檔案等資訊,接下來就進入了由“(gdb)”開頭的命令列介面了。

(1)檢視檔案

在Gdb中鍵入”l”(list)就可以檢視所載入的檔案,如下所示:

 

 

注意

在Gdb的命令中都可使用縮略形式的命令,如“l”代便“list”,“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列出的原始碼中明確地給出了對應的行號,這樣就可以大大地方便程式碼的定位。

(2)設定斷點

設定斷點是除錯程式中是一個非常重要的手段,它可以使程式到一定位置暫停它的執行。因此,程式設計師在該位置處可以方便地檢視變數的值、堆疊情況等,從而找出程式碼的癥結所在。

在Gdb中設定斷點非常簡單,只需在”b”後加入對應的行號即可(這是最常用的方式,另外還有其他方式設定斷點)。如下所示:

 

(Gdb) b 6

Breakpoint 1 at 0x804846d: file test.c, line 6.

 

要注意的是,在Gdb中利用行號設定斷點是指程式碼執行到對應行之前將其停止,如上例中,程式碼執行到第五行之前暫停(並沒有執行第五行)。

(3)檢視斷點情況

在設定完斷點之後,使用者可以鍵入”info b”來檢視設定斷點情況,在Gdb中可以設定多個斷點。

 

(Gdb) info b

Num Type Disp Enb Address What

1 breakpoint keep y 0x0804846d in main at test.c:6

 

(4)執行程式碼

接下來就可執行程式碼了,Gdb預設從首行開始執行程式碼,可鍵入”r”(run)即可(若想從程式中指定行開始執行,可在r後面加上行號)。

 

(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);

 

可以看到,程式執行到斷點處就停止了。

(5)檢視變數值

在程式停止執行之後,程式設計師所要做的工作是檢視斷點處的相關變數值。在Gdb中只需鍵入”p”+變數值即可,如下所示:

 

(Gdb) p n

$1 = 0

(Gdb) p i

$2 = 134518440

 

在此處,為什麼變數”i”的值為如此奇怪的一個數字呢?原因就在於程式是在斷點設定的對應行之前停止的,那麼在此時,並沒有把”i”的數值賦為零,而只是一個隨機的數字。但變數”n”是在第四行賦值的,故在此時已經為零。

 

小技巧

Gdb在顯示變數值時都會在對應值之前加上”$N”標記,它是當前變數值的引用標記,所以以後若想再次引用此變數就可以直接寫作”$N”,而無需寫冗長的變數名。

 

(6)單步執行

單步執行可以使用命令”n”(next)或”s”(step),它們之間的區別在於:若有函式呼叫的時候,”s”會進入該函式而”n”不會進入該函式。因此,”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”後,程式顯示函式sum的執行結果並向下執行,而使用”s”後則進入到sum函式之中單步執行。

(7)恢復程式執行

在檢視完所需變數及堆疊情況後,就可以使用命令”c”(continue)恢復程式的正常執行了。這時,它會把剩餘還未執行的程式執行完,並顯示剩餘程式中的執行結果。以下是之前使用”n”命令恢復後的執行結果:

 

(Gdb) c

Continuing.

The sum of 1-50 is :1275

 

Program exited with code 031.

 

可以看出,程式在執行完後退出,之後程式處於“停止狀態”。

 

小知識

在Gdb中,程式的執行狀態有“執行”、“暫停”和“停止”三種,其中“暫停”狀態為程式遇到了斷點或觀察點之類的,程式暫時停止執行,而此時函式的地址、函式引數、函式內的區域性變數都會被壓入“棧”(Stack)中。故在這種狀態下可以檢視函式的變數值等各種屬性。但在函式處於“停止”狀態之後,“棧”就會自動撤銷,它也就無法檢視各種資訊了。

Gdb基本命令

Gdb的命令可以通過檢視help進行查詢,由於Gdb的命令很多,因此Gdb的help將其分成了很多種類(class),使用者可以通過進一步檢視相關class找到相應命令。如下所示:

 

(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各個分類的命令,注意底部的加粗部分說明其為分類命令。接下來可以具體查詢各分類種的命令。如下所示:

 

(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.

 

當然,若使用者已知命令名,直接鍵入“help [command]”也是可以的。

Gdb中的命令主要分為以下幾類:工作環境相關命令、設定斷點與恢復命令、原始碼檢視命令、檢視執行資料相關命令及修改執行引數命令。以下就分別對這幾類的命令進行講解。

1.工作環境相關命令

Gdb中不僅可以除錯所執行的程式,而且還可以對程式相關的工作環境進行相應的設定,甚至還可以使用shell中的命令進行相關的操作,其功能極其強大。表3.10所示列出了Gdb常見工作環境相關命令。

表3.10 Gdb工作環境相關命令

命 令 格 式

含義

set args執行時的引數

指定執行時引數,如:set args 2

show args

檢視設定好的執行引數

path dir

設定程式的執行路徑

show paths

檢視程式的執行路徑

set enVironment var [=value]

設定環境變數

show enVironment [var]

檢視環境變數

cd dir

進入到dir目錄,相當於shell中的cd命令

pwd

顯示當前工作目錄

shell command

執行shell的command命令

2.設定斷點與恢復命令

Gdb中設定斷點與恢復的常見命令如表3.11所示。

表3.11 Gdb設定斷點與恢復相關命令

命 令 格 式

含義

bnfo b

檢視所設斷點

break 行號或函式名 <條件表示式>

設定斷點

tbreak 行號或函式名 <條件表示式>

設定臨時斷點,到達後被自動刪除

delete [斷點號]

刪除指定斷點,其斷點號為”info b”中的第一欄。若預設斷點號則刪除所有斷點

disable [斷點號]]

停止指定斷點,使用”info b”仍能檢視此斷點。同delete一樣,省斷點號則停止所有斷點

enable [斷點號]

啟用指定斷點,即啟用被disable停止的斷點

condition [斷點號] <條件表示式>

修改對應斷點的條件

ignore [斷點號]<num>

在程式執行中,忽略對應斷點num次

step

單步恢復程式執行,且進入函式呼叫

next

單步恢復程式執行,但不進入函式呼叫

finish

執行程式,直到當前函式完成返回

c

繼續執行函式,直到函式結束或遇到新的斷點

 

由於設定斷點在Gdb的除錯中非常重要,所以在此再著重講解一下Gdb中設定斷點的方法。

Gdb中設定斷點有多種方式:其一是按行設定斷點,設定方法在3.5.1節已經指出,在此就不重複了。另外還可以設定函式斷點和條件斷點,在此結合上一小節的程式碼,具體介紹後兩種設定斷點的方法。

① 函式斷點

Gdb中按函式設定斷點只需把函式名列在命令”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

 

要注意的是,此時的斷點實際是在函式的定義處,也就是在16行處(注意第16行還未執行)。

② 條件斷點

Gdb中設定條件斷點的格式為:b 行數或函式名 if 表示式。具體例項如下所示:

 

(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行(也就是執行完第7行的for迴圈)設定了一個“i==0”的條件斷點,在程式執行之後可以看出,程式確實在i為10時暫停執行。

3.Gdb中原始碼檢視相關命令

在Gdb中可以檢視原始碼以方便其他操作,它的常見相關命令如表3.12所示:

表3.12 Gdb原始碼檢視相關相關命令

命 令 格 式

含義

list <行號>|<函式名>

檢視指定位置程式碼

file [檔名]

載入指定檔案

forward-search 正規表示式

原始碼前向搜尋

reverse-search 正規表示式

原始碼後向搜尋

dir dir

停止路徑名

show directories

顯示定義了的原始檔搜尋路徑

info line

顯示載入到Gdb記憶體中的程式碼

4.Gdb中檢視執行資料相關命令

Gdb中檢視執行資料是指當程式處於“執行”或“暫停”狀態時,可以檢視的變數及表示式的資訊,其常見命令如表3.13所示:

表3.13 Gdb檢視執行資料相關命令

命 令 格 式

含義

print 表示式|變數

檢視程式執行時對應表示式和變數的值

x <n/f/u>

檢視記憶體變數內容。其中n為整數表示顯示記憶體的長度,f表示顯示的格式,u表示從當前地址往後請求顯示的位元組數

display 表示式

設定在單步執行或其他情況中,自動顯示的對應表示式的內容

5.Gdb中修改執行引數相關命令

Gdb還可以修改執行時的引數,並使該變數按照使用者當前輸入的值繼續執行。它的設定方法為:在單步執行的過程中,鍵入命令“set 變數=設定值”。這樣,在此之後,程式就會按照該設定的值執行了。下面,筆者結合上一節的程式碼將n的初始值設為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.

 

可以看到,最後的執行結果確實比之前的值大了4。

 

 

Gdb的使用切記點:

· 在Gcc編譯選項中一定要加入”-g”。

· 只有在程式碼處於“執行”或“暫停”狀態時才能檢視變數值。

· 設定斷點後程式在指定行之前停止。

Make工程管理器

到此為止,讀者已經瞭解瞭如何在Linux下使用編輯器編寫程式碼,如何使用Gcc把程式碼編譯成可執行檔案,還學習瞭如何使用Gdb來除錯程式,那麼,所有的工作看似已經完成了,為什麼還需要Make這個工程管理器呢?

所謂工程管理器,顧名思義,是指管理較多的檔案的。讀者可以試想一下,有一個上百個檔案的程式碼構成的專案,如果其中只有一個或少數幾個檔案進行了修改,按照之前所學的Gcc編譯工具,就不得不把這所有的檔案重新編譯一遍,因為編譯器並不知道哪些檔案是最近更新的,而只知道需要包含這些檔案才能把原始碼編譯成可執行檔案,於是,程式設計師就不能不再重新輸入數目如此龐大的檔名以完成最後的編譯工作。

但是,請讀者仔細回想一下本書在3.1.2節中所闡述的編譯過程,編譯過程是分為編譯、彙編、連結不同階段的,其中編譯階段僅檢查語法錯誤以及函式與變數的宣告是否正確宣告瞭,在連結階段則主要完成是函式連結和全域性變數的連結。因此,那些沒有改動的原始碼根本不需要重新編譯,而只要把它們重新連結進去就可以了。所以,人們就希望有一個工程管理器能夠自動識別更新了的檔案程式碼,同時又不需要重複輸入冗長的命令列,這樣,Make工程管理器也就應運而生了。

實際上,Make工程管理器也就是個“自動編譯管理器”,這裡的“自動”是指它能夠根據檔案時間戳自動發現更新過的檔案而減少編譯的工作量,同時,它通過讀入Makefile檔案的內容來執行大量的編譯工作。使用者只需編寫一次簡單的編譯語句就可以了。它大大提高了實際專案的工作效率,而且幾乎所有Linux下的專案程式設計均會涉及到它,希望讀者能夠認真學習本節內容。

Makefile基本結構

Makefile是Make讀入的惟一配置檔案,因此本節的內容實際就是講述Makefile的編寫規則。在一個Makefile中通常包含如下內容:

· 需要由make工具建立的目標體(target),通常是目標檔案或可執行檔案;

· 要建立的目標體所依賴的檔案(dependency_file);

· 建立每個目標體時需要執行的命令(command)。

它的格式為:

 

target: dependency_files

command

 

例如,有兩個檔案分別為hello.c和hello.h,建立的目標體為hello.o,執行的命令為gcc編譯指令:gcc –c hello.c,那麼,對應的Makefile就可以寫為:

 

#The simplest example

hello.o: hello.c hello.h

gcc –c hello.c –o hello.o

 

接著就可以使用make了。使用make的格式為:make target,這樣make就會自動讀入Makefile(也可以是首字母小寫makefile)並執行對應target的command語句,並會找到相應的依賴檔案。如下所示:

 

[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執行了“hello.o”對應的命令語句,並生成了“hello.o”目標體。

 

 

注意

在Makefile中的每一個command前必須有“Tab”符,否則在執行make命令時會出錯。

Makefile變數

上面示例的Makefile在實際中是幾乎不存在的,因為它過於簡單,僅包含兩個檔案和一個命令,在這種情況下完全不必要編寫Makefile而只需在Shell中直接輸入即可,在實際中使用的Makefile往往是包含很多的檔案和命令的,這也是Makefile產生的原因。下面就可給出稍微複雜一些的Makefile進行講解:

 

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

 

在這個Makefile中有三個目標體(target),分別為sunq、kang.o和yul.o,其中第一個目標體的依賴檔案就是後兩個目標體。如果使用者使用命令“make sunq”,則make管理器就是找到sunq目標體開始執行。

這時,make會自動檢查相關檔案的時間戳。首先,在檢查“kang.o”、“yul.o”和“sunq”三個檔案的時間戳之前,它會向下查詢那些把“kang.o”或“yul.o”做為目標檔案的時間戳。比如,“kang.o”的依賴檔案為:“kang.c”、“kang.h”、“head.h”。如果這些檔案中任何一個的時間戳比“kang.o”新,則命令“gcc –Wall –O -g –c kang.c -o kang.o”將會執行,從而更新檔案“kang.o”。在更新完“kang.o”或“yul.o”之後,make會檢查最初的“kang.o”、“yul.o”和“sunq”三個檔案,只要檔案“kang.o”或“yul.o”中的任比檔案時間戳比“sunq”新,則第二行命令就會被執行。這樣,make就完成了自動檢查時間戳的工作,開始執行編譯工作。這也就是Make工作的基本流程。

接下來,為了進一步簡化編輯和維護Makefile,make允許在Makefile中建立和使用變數。變數是在Makefile中定義的名字,用來代替一個文字字串,該文字字串稱為該變數的值。在具體要求下,這些值可以代替目標體、依賴檔案、命令以及makefile檔案中其它部分。在Makefile中的變數定義有兩種方式:一種是遞迴展開方式,另一種是簡單方式。

遞迴展開方式定義的變數是在引用在該變數時進行替換的,即如果該變數包含了對其他變數的應用,則在引用該變數時一次性將內嵌的變數全部展開,雖然這種型別的變數能夠很好地完成使用者的指令,但是它也有嚴重的缺點,如不能在變數後追加內容(因為語句:CFLAGS = $(CFLAGS) -O在變數擴充套件過程中可能導致無窮迴圈)。

為了避免上述問題,簡單擴充套件型變數的值在定義處展開,並且只展開一次,因此它不包含任何對其它變數的引用,從而消除變數的巢狀引用。

遞迴展開方式的定義格式為:VAR=var

簡單擴充套件方式的定義格式為:VAR:=var

Make中的變數使用均使用格式為:$(VAR)

 

 

注意

變數名是不包括“:”、“#”、“=”結尾空格的任何字串。同時,變數名中包含字母、數字以及下劃線以外的情況應儘量避免,因為它們可能在將來被賦予特別的含義。

變數名是大小寫敏感的,例如變數名“foo”、“FOO”、和“Foo”代表不同的變數。

推薦在makefile內部使用小寫字母作為變數名,預留大寫字母作為控制隱含規則引數或使用者過載命令選項引數的變數名。

 

下面給出了上例中用變數替換修改後的Makefile,這裡用OBJS代替kang.o和yul.o,用CC代替Gcc,用CFLAGS代替“-Wall -O –g”。這樣在以後修改時,就可以只修改變數定義,而不需要修改下面的定義實體,從而大大簡化了Makefile維護的工作量。

經變數替換後的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中的變數分為使用者自定義變數、預定義變數、自動變數及環境變數。如上例中的OBJS就是使用者自定義變數,自定義變數的值由使用者自行設定,而預定義變數和自動變數為通常在Makefile都會出現的變數,其中部分有預設值,也就是常見的設定值,當然使用者可以對其進行修改。

預定義變數包含了常見編譯器、彙編器的名稱及其編譯選項。下表3.14列出了Makefile中常見預定義變數及其部分預設值。

表3.14 Makefile中常見預定義變數

命 令 格 式

含義

AR

庫檔案維護程式的名稱,預設值為ar

AS

彙編程式的名稱,預設值為as

CC

C編譯器的名稱,預設值為cc

CPP

C預編譯器的名稱,預設值為$(CC) –E

CXX

C++編譯器的名稱,預設值為g++

FC

FORTRAN編譯器的名稱,預設值為f77

RM

檔案刪除程式的名稱,預設值為rm –f

ARFLAGS

庫檔案維護程式的選項,無預設值

ASFLAGS

彙編程式的選項,無預設值

CFLAGS

C編譯器的選項,無預設值

CPPFLAGS

C預編譯的選項,無預設值

CXXFLAGS

C++編譯器的選項,無預設值

FFLAGS

FORTRAN編譯器的選項,無預設值

 

可以看出,上例中的CC和CFLAGS是預定義變數,其中由於CC沒有采用預設值,因此,需要把“CC=Gcc”明確列出來。

由於常見的Gcc編譯語句中通常包含了目標檔案和依賴檔案,而這些檔案在Makefile檔案中目標體的一行已經有所體現,因此,為了進一步簡化Makefile的編寫,就引入了自動變數。自動變數通常可以代表編譯語句中出現目標檔案和依賴檔案等,並且具有本地含義(即下一語句中出現的相同變數代表的是下一語句的目標檔案和依賴檔案)。下表3.15列出了Makefile中常見自動變數。

表3.15Makefile中常見自動變數

命令格式

含義

$*

不包含副檔名的目標檔名稱

$+

所有的依賴檔案,以空格分開,並以出現的先後為序,可能包含重複的依賴檔案

$<

第一個依賴檔案的名稱

$?

所有時間戳比目標檔案晚的依賴檔案,並以空格分開

命令格式

含義

$@

目標檔案的完整名稱

$^

所有不重複的依賴檔案,以空格分開

$%

如果目標是歸檔成員,則該變數表示目標的歸檔成員名稱

 

自動變數的書寫比較難記,但是在熟練了之後會非常的方便,請讀者結合下例中的自動變數改寫的Makefile進行記憶。

 

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中還可以使用環境變數。使用環境變數的方法相對比較簡單,make在啟動時會自動讀取系統當前已經定義了的環境變數,並且會建立與之具有相同名稱和數值的變數。但是,如果使用者在Makefile中定義了相同名稱的變數,那麼使用者自定義變數將會覆蓋同名的環境變數。

Makefile規則

Makefile的規則是Make進行處理的依據,它包括了目標體、依賴檔案及其之間的命令語句。一般的,Makefile中的一條語句就是一個規則。在上面的例子中,都顯示地指出了Makefile中的規則關係,如“$(CC) $(CFLAGS) -c $< -o $@”,但為了簡化Makefile的編寫,make還定義了隱式規則和模式規則,下面就分別對其進行講解。

1.隱式規則

隱含規則能夠告訴make怎樣使用傳統的技術完成任務,這樣,當使用者使用它們時就不必詳細指定編譯的具體細節,而只需把目標檔案列出即可。Make會自動搜尋隱式規則目錄來確定如何生成目標檔案。如上例就可以寫成:

 

OBJS = kang.o yul.o

CC = Gcc

CFLAGS = -Wall -O -g

sunq : $(OBJS)

$(CC) $^ -o $@

 

為什麼可以省略後兩句呢?因為Make的隱式規則指出:所有“.o”檔案都可自動由“.c”檔案使用命令“$(CC) $(CPPFLAGS) $(CFLAGS) -c file.c –o file.o”生成。這樣“kang.o”和“yul.o”就會分別呼叫“$(CC) $(CFLAGS) -c kang.c -o kang.o”和“$(CC) $(CFLAGS) -c yul.c -o yul.o”生成。

 

 

注意

在隱式規則只能查詢到相同檔名的不同字尾名檔案,如”kang.o”檔案必須由”kang.c”檔案生成。

 

下表3.16給出了常見的隱式規則目錄:

表3.16 Makefile中常見隱式規則目錄

對應語言字尾名

規則

C編譯:.c變為.o

$(CC) –c $(CPPFLAGS) $(CFLAGS)

C++編譯:.cc或.C變為.o

$(CXX) -c $(CPPFLAGS) $(CXXFLAGS)

Pascal編譯:.p變為.o

$(PC) -c $(PFLAGS)

Fortran編譯:.r變為-o

$(FC) -c $(FFLAGS)

2.模式規則

模式規則是用來定義相同處理規則的多個檔案的。它不同於隱式規則,隱式規則僅僅能夠用make預設的變數來進行操作,而模式規則還能引入使用者自定義變數,為多個檔案建立相同的規則,從而簡化Makefile的編寫。

模式規則的格式類似於普通規則,這個規則中的相關檔案前必須用“%”標明。使用模式規則修改後的Makefile的編寫如下:

 

OBJS = kang.o yul.o

CC = Gcc

CFLAGS = -Wall -O -g

sunq : $(OBJS)

$(CC) $^ -o $@

%.o : %.c

$(CC) $(CFLAGS) -c $< -o $@

Make使用

使用make管理器非常簡單,只需在make命令的後面鍵入目標名即可建立指定的目標,如果直接執行make,則建立Makefile中的第一個目標。

此外make還有豐富的命令列選項,可以完成各種不同的功能。下表3.17列出了常用的make命令列選項。

表3.17 make的命令列選項

命令格式

含 義

-C dir

讀入指定目錄下的Makefile

-f file

讀入當前目錄下的file檔案作為Makefile

命令格式

含 義

-i

忽略所有的命令執行錯誤

-I dir

指定被包含的Makefile所在目錄

-n

只列印要執行的命令,但不執行這些命令

-p

顯示make變數資料庫和隱含規則

-s

在執行命令時不顯示命令

-w

如果make在執行過程中改變目錄,則列印當前目錄名

使用autotools

在上一小節,讀者已經瞭解到了make專案管理器的強大功能。的確,Makefile可以幫助make完成它的使命,但要承認的是,編寫Makefile確實不是一件輕鬆的事,尤其對於一個較大的專案而言更是如此。那麼,有沒有一種輕鬆的手段生成Makefile而同時又能讓使用者享受make的優越性呢?本節要講的autotools系列工具正是為此而設的,它只需使用者輸入簡單的目標檔案、依賴檔案、檔案目錄等就可以輕鬆地生成Makefile了,這無疑是廣大使用者的所希望的。另外,這些工具還可以完成系統配置資訊的收集,從而可以方便地處理各種移植性的問題。也正是基於此,現在Linux上的軟體開發一般都用autotools來製作Makefile,讀者在後面的講述中就會了解到。

autotools使用流程

正如前面所言,autotools是系列工具,讀者首先要確認系統是否裝了以下工具(可以用which命令進行檢視)。

· aclocal

· autoscan

· autoconf

· autoheader

· automake

使用autotools主要就是利用各個工具的指令碼檔案以生成最後的Makefile。其總體流程是這樣的:

· 使用aclocal生成一個“aclocal.m4”檔案,該檔案主要處理本地的巨集定義;

· 改寫“configure.scan”檔案,並將其重新命名為“configure.in”,並使用autoconf檔案生成configure檔案。

接下來,筆者將通過一個簡單的hello.c例子帶領讀者熟悉autotools生成makefile的過程,由於在這過程中有涉及到較多的指令碼檔案,為了更清楚地瞭解相互之間的關係,強烈建議讀者實際動手操作以體會其整個過程。

1.autoscan

它會在給定目錄及其子目錄樹中檢查原始檔,若沒有給出目錄,就在當前目錄及其子目錄樹中進行檢查。它會搜尋原始檔以尋找一般的移植性問題並建立一個檔案“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首先會嘗試去讀入“configure.ac”(同configure.in的配置檔案)檔案,此時還沒有建立該配置檔案,於是它會自動生成一個“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

 

下面對這個指令碼檔案進行解釋:

· 以“#”號開始的行為註釋。

· AC_PREREQ巨集宣告本檔案要求的autoconf版本,如本例使用的版本2.59。

· AC_INIT巨集用來定義軟體的名稱和版本等資訊,在本例中省略了BUG-REPORT-ADDRESS,一般為作者的e-mail。

· AM_INIT_AUTOMAKE是筆者另加的,它是automake所必備的巨集,也同前面一樣,PACKAGE是所要產生軟體套件的名稱,VERSION是版本編號。

· AC_CONFIG_SRCDIR巨集用來偵測所指定的原始碼檔案是否存在,來確定原始碼目錄的有

效性。在此處為當前目錄下的hello.c。

· AC_CONFIG_HEADER巨集用於生成config.h檔案,以便autoheader使用。

· AC_CONFIG_FILES巨集用於生成相應的Makefile檔案。

· 中間的註釋間可以新增分別使用者測試程式、測試函式庫、測試標頭檔案等巨集定義。

接下來首先執行aclocal,生成一個“aclocal.m4”檔案,該檔案主要處理本地的巨集定義。如下所示:

 

[root@localhost automake]# aclocal

 

再接著執行autoconf,生成“configure”可執行檔案。如下所示:

 

[root@localhost automake]# autoconf

[root@localhost automake]# ls

aclocal.m4 autom4te.cache autoscan.log configure configure.in hello.c

3.autoheader

接著使用autoheader命令,它負責生成config.h.in檔案。該工具通常會從“acconfig.h”檔案中複製使用者附加的符號定義,因此此處沒有附加符號定義,所以不需要建立“acconfig.h”檔案。如下所示:

 

[root@localhost automake]# autoheader

4.automake

這一步是建立Makefile很重要的一步,automake要用的指令碼配置檔案是Makefile.am,使用者需要自己建立相應的檔案。之後,automake工具轉換成Makefile.in。在該例中,筆者建立的檔案為Makefile.am如下所示:

 

AUTOMAKE_OPTIONS=foreign

bin_PROGRAMS= hello

hello_SOURCES= hello.c

 

下面對該指令碼檔案的對應項進行解釋。

· 其中的AUTOMAKE_OPTIONS為設定automake的選項。由於GNU(在第1章中已經有所介紹)對自己釋出的軟體有嚴格的規範,比如必須附帶許可證宣告檔案COPYING等,否則automake執行時會報錯。automake提供了三種軟體等級:foreign、gnu和gnits,讓使用者選擇採用,預設等級為gnu。在本例使用foreign等級,它只檢測必須的檔案。

· bin_PROGRAMS定義要產生的執行檔名。如果要產生多個執行檔案,每個檔名用空格隔開。

· hello_SOURCES定義“hello”這個執行程式所需要的原始檔案。如果”hello”這個程式是由多個原始檔案所產生的,則必須把它所用到的所有原始檔案都列出來,並用空格隔開。例如:若目標體“hello”需要“hello.c”、“sunq.c”、“hello.h”三個依賴檔案,則定義hello_SOURCES=hello.c sunq.c hello.h。要注意的是,如果要定義多個執行檔案,則對每個執行程式都要定義相應的file_SOURCES。

接下來可以使用automake對其生成“configure.in”檔案,在這裡使用選項“—adding-missing”可以讓automake自動新增有一些必需的指令碼檔案。如下所示:

 

[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.執行configure

在這一步中,通過執行自動配置設定檔案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

可以看到,在執行configure時收集了系統的資訊,使用者可以在configure命令中對其進行方便地配置。在./configure的自定義引數有兩種,一種是開關式(--enable-XXX或--disable-XXX),另一種是開放式,即後面要填入一串字元(--with-XXX=yyyy)引數。讀者可以自行嘗試其使用方法。另外,讀者可以檢視同一目錄下的”config.log”檔案,以方便除錯之用。

到此為止,makefile就可以自動生成了。回憶整個步驟,使用者不再需要定製不同的規則,而只需要輸入簡單的檔案及目錄名即可,這樣就大大方便了使用者的使用。下面的圖3.9總結了上述過程:

 

圖3.9 autotools生成Makefile流程圖

使用autotools所生成的Makefile

autotools生成的Makefile除具有普通的編譯功能外,還具有以下主要功能(感興趣的讀者可以檢視這個簡單的hello.c程式的makefile):

1.make

鍵入make預設執行”make all”命令,即目標體為all,其執行情況如下所示:

 

[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

此時在本目錄下就生成了可執行檔案“hello”,執行“./hello”能出現正常結果,如下所示:

 

[root@localhost automake]# ./hello

Hello!Autoconf!

2.make install

此時,會把該程式安裝到系統目錄中去,如下所示:

 

[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'

 

此時,若直接執行hello,也能出現正確結果,如下所示:

 

[root@localhost automake]# hello

Hello!Autoconf!

3.make clean

此時,make會清除之前所編譯的可執行檔案及目標檔案(object file, *.o),如下所示:

 

[root@localhost automake]# make clean

test -z "hello" || rm -f hello

rm -f *.o

4.make dist

此時,make將程式和相關的文件打包為一個壓縮文件以供釋出,如下所示:

 

[root@localhost automake]# make dist

[root@localhost automake]# ls hello-1.0-tar.gz

hello-1.0-tar.gz

 

可見該命令生成了一個hello-1.0-tar.gz的壓縮檔案。

由上面的講述讀者不難看出,autotools確實是軟體維護與釋出的必備工具,也鑑於此,如今GUN的軟體一般都是由automake來製作的。

 

 

想一想

對於automake製作的這類軟體,應如何安裝呢?

Vi使用練習

1.實驗目的

通過指定指令的Vi操作練習,使讀者能夠熟練使用Vi中的常見操作,並且熟悉Vi的三種模式,如果讀者能夠熟練掌握實驗內容中所要求的內容,則表明對Vi的操作已經很熟練了。

2.實驗內容

(1)在“/root”目錄下建一個名為“/Vi”的目錄。

(2)進入“/Vi”目錄。

(3)將檔案“/etc/inittab”複製到“/Vi”目錄下。

(4)使用Vi開啟“/Vi”目錄下的inittab。

(5)設定行號,指出設定initdefault(類似於“id:5:initdefault”)的所在行號。

(6)將游標移到該行。

(7)複製該行內容。

(8)將游標移到最後一行行首。

(9)貼上複製行的內容。

(10)撤銷第9步的動作。

(11)將游標移動到最後一行的行尾。

(12)貼上複製行的內容。

(13)游標移到“si::sysinit:/etc/rc.d/rc.sysinit”。

(14)刪除該行。

(15)存檔但不退出。

(16)將游標移到首行。

(17)插入模式下輸入“Hello,this is Vi world!”。

(18)返回命令列模式。

(19)向下查詢字串“0:wait”。

(20)再向上查詢字串“halt”。

(21)強制退出Vi,不存檔。

分別指出每個命令處於何種模式下?

3.實驗步驟

(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.實驗結果

該實驗最後的結果只對“/root/inittab”增加了一行復制的內容:“id:5:initdefault”。

用Gdb除錯有問題的程式

1.實驗目的

通過除錯一個有問題的程式,使讀者進一步熟練使用Vi操作,而且熟練掌握Gcc編譯命令及Gdb的除錯命令,通過對有問題程式的跟蹤除錯,進一步提高發現問題和解決問題的能力。這是一個很小的程式,只有35行,希望讀者認真除錯。

2.實驗內容

(1)使用Vi編輯器,將以下程式碼輸入到名為greet.c的檔案中。此程式碼的原意為輸出倒序main函式中定義的字串,但結果顯示沒有輸出。程式碼如下所示:

 

#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”選項以方便之後的除錯。

(3)執行生成的可執行檔案,觀察執行結果。

(4)使用Gdb除錯程式,通過設定斷點、單步跟蹤,一步步找出錯誤所在。

(5)糾正錯誤,更改源程式並得到正確的結果。

3.實驗步驟

(1)在工作目錄上新建檔案greet.c,並用Vi啟動:vi greet.c。

(2)在Vi中輸入以上程式碼。

(3)在Vi中儲存並退出:wq。

(4)用Gcc編譯:gcc -g greet.c -o greet。

(5)執行greet:./greet,輸出為:

 

The original string is Embedded Linux

The string afterward is

 

可見,該程式沒有能夠倒序輸出。

(6)啟動Gdb除錯:gdb greet。

(7)檢視原始碼,使用命令“l”。

(8)在30行(for迴圈處)設定斷點,使用命令“b 30”。

(9)在33行(printf函式處)設定斷點,使用命令“b 33”。

(10)檢視斷點設定情況,使用命令“info b”。

(11)執行程式碼,使用命令“r”。

(12)單步執行程式碼,使用命令“n”。

(13)檢視暫停點變數值,使用命令“p string2[size - i]”。

(14)繼續單步執行程式碼數次,並使用命令檢視,發現string2[size-1]的值正確。