GCC编译器-<嵌入式Linux应用程序开发标准教程>-华清远见


3.3 gcc编译器
GNU CC(简称为gcc)是GNU项目中符合ANSI C标准的编译系统,能够编译用C、C++和Object C等
语言编写的程序。gcc不仅功能强大,而且可以编译如C、C++、Object C、Java、Fortran、Pascal、Modula-3
和Ada等多种语言,而且gcc是一个交叉平台编译器,它能够在当前CPU平台上为多种不同体系结构的
硬件平台开发软件,因此尤其适合在嵌入式领域的开发编译。本章中的示例,除非特别注明,否则均采用
4.x.x的gcc版本。
表3.6所示为gcc支持编译源文件的后缀及其解释。
表3.6 gcc所支持后缀名解释
后  缀  名  所对应的语言  后  缀  名  所对应的语言
.c  C原始程序  .s/.S  汇编语言原始程序
.C/.cc/.cxx  C++原始程序  .h  预处理文件(头文件)
.m  Objective-C原始程序  .o  目标文件
.i  已经过预处理的C原始程序  .a/.so  编译后的库文件
.ii  已经过预处理的C++原始程序  … … 
3.3.1 gcc编译流程解析
如本章开头提到的,gcc的编译流程分为了4个步骤,分别为:
  预处理(Pre-Processing);
  编译(Compiling);
  汇编(Assembling);
  链接(Linking)。
下面就具体来查看一下gcc是如何完成以上4个步骤的。
首先看一下hello.c的源代码:
#include <stdio.h> 

int main() 

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

(1)预处理阶段。
在该阶段,对包含的头文件(#include)和宏定义(#define、#ifdef等)进行处理。在上述代码的预处理过
程中,编译器将包含的头文件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 %ebp 
movl %esp, %ebp 

subl $8, %esp 
andl $-16, %esp 
movl $0, %eax 
addl $15, %eax 
addl $15, %eax 
shrl $4, %eax 
sall $4, %eax 
subl %eax, %esp 
subl $12, %esp 
pushl $.LC0 
call puts 
addl $16, %esp 
movl $0, %eax 
leave 
ret 
.size main, .-main 
.ident "GCC: (GNU) 4.0.0 200XYZ19 (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! 
3.3.2 gcc编译选项分析
gcc有超过100个可用选项,主要包括总体选项、告警和出错选项、优化选项和体系结构相关选项。以下
对每一类中最常用的选项进行讲解。

(1)常用选项。
gcc的常用选项如表3.7所示,很多在前面的示例中已经有所涉及。
表3.7 gcc常用选项列表
选  项  含  义
-c  只编译不链接,生成目标文件“.o”
-S  只编译不汇编,生成汇编代码
-E  只进行预编译,不做其他处理
-g  在可执行程序中包含标准调试信息
-o file  将file文件指定为输出文件
-v  打印出编译器内部编译各过程的命令行信息和编译器的版本
-I dir  在头文件的搜索路径列表中添加dir目录
前一小节已经讲解了“-c”、“-E”、“-o”、“-S”选项的使用方法,在此主要讲解另外2个非常常用的库依
赖选项“-I dir”。
  “-I dir”
正如上表中所述,“-I dir”选项可以在头文件的搜索路径列表中添加dir目录。由于Linux中头文件都默认
放到了“/usr/include/”目录下,因此,当用户希望添加放置在其他位置的头文件时,就可以通过“-I dir”
选项来指定,这样,gcc就会到相应的位置查找对应的目录。
比如在“/root/workplace/gcc”下有两个文件:
/* hello1.c */ 
#include<my.h> 
int main() 

printf("Hello!!\n"); 
return 0; 

/* my.h */ 
#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”选项了。
(2)库选项。
gcc库选项如表3.8所示。
表3.8 gcc库选项列表
选  项  含  义
-static  进行静态编译,即链接静态库,禁止使用动态库
-shared 
1.可以生成动态库文件
2.进行动态编译,尽可能地链接动态库,只有当没有动态库时才会链接同名的静态库
(默认选项,即可省略)
-L dir  在库文件的搜索路径列表中添加dir目录 

-lname 
链接称为libname.a(静态库)或者libname.so(动态库)的库文件。若两个库都存在,
则根据编译方式(-static还是-shared)而进行链接
-fPIC(或-fpic)
生成使用相对地址的位置无关的目标代码(Position Independent Code)。然后通常使用
gcc的-static选项从该PIC目标文件生成动态库文件
我们通常需要将一些常用的公共函数编译并集成到二进制文件(Linux的ELF格式文件),以便其他程
复地使用该文件中的函数,此时将这种文件叫做函数库,使用函数库不仅能够节省很多内存和存储器的
源,而且更重要的是大大降低开发难度和开销,提高开发效率并增强程序的结构性。实际上,在Linux
个程序都会链接到一个或者多个库。比如使用C函数的程序会链接到C运行时库,Qt应用程序会链接
支持的相关图形库等。
函数库有静态库和动态库两种,静态库是一系列的目标文件(.o 文件)的归档文件(文件名格
libname.a),如果在编译某个程序时链接静态库,则链接器将会搜索静态库,从中提取出它所需要的
文件并直接复制到该程序的可执行二进制文件(ELF格式文件)之中;动态库(文件名格式为libname
版本号.次版本号.发行号])在程序编译时并不会被链接到目标代码中,而是在程序运行时才被载入。
下面举一个简单的例子,讲解如何怎么创建和使用这两种函数库。
首先创建unsgn_pow.c文件,它包含unsgn_pow()函数的定义,具体代码如下所示。
/* unsgn_pow.c:库程序*/ 
unsigned long long unsgn_pow(unsigned int x, unsigned int y) 

unsigned long long res = 1; 
if (y == 0) 

res = 1; 

else if (y == 1) 

res = x; 

else 

res = x * unsgn_pow(x, y - 1); 

return res; 

然后创建pow_test.c文件,它会调用unsgn_pow()函数。
/* pow_test.c */ 
#include <stdio.h> 
#include <stdlib.h> 
int main(int argc, char *argv[]) 

 unsigned int x, y; 
 unsigned long long res; 
 if ((argc < 3) || (sscanf(argv[1], "%u", &x) != 1) 
|| (sscanf(argv[2], "%u", &y)) != 1) 

  printf("Usage: pow base exponent\n"); 
exit(1); 

 res = unsgn_pow(x, y); 
 printf("%u ^ %u = %u\n", x, y, res); 
exit(0); 

我们用unsgn_pow.c文件可以制作一个函数库。下面分别讲解怎么生成静态库和动态库。
  静态库的创建和使用。
创建静态库比较简单,使用归档工具ar将一些目标文件集成在一起。
[root@localhost lib]# gcc -c unsgn_pow.c
[root@localhost lib]# ar rcsv libpow.a unsgn_pow.o 
a - unsgn_pow.o 
下面编译主程序,它将会链接到刚生成的静态库libpow.a。具体运行结果如下所示。
[root@localhost lib]# gcc -o pow_test pow_test.c -L. –lpow 
[root@localhost lib]# ./pow_test 2 10
2 ^ 10 = 1024 
其中,选项“-L dir”的功能与“-I dir”类似,能够在库文件的搜索路径列表中添加dir目录,而“-lna
选项指示编译时链接到库文件libname.a 或者libname.so。本实例中,程序pow_test.c需要使用当前目录
的一个静态库libpow.a。
  动态库的创建和使用。
首先使用gcc的-fPIC选项为动态库构造一个目标文件
[root@localhost lib]# gcc -fPIC -Wall -c unsgn_pow.c
接下来,使用-shared选项和已创建的位置无关目标代码,生成一个动态库libpow.so。
[root@localhost lib]# gcc -shared -o libpow.so unsgn_pow.o
下面编译主程序,它将会链接到刚生成的动态库libpow.so。
[root@localhost lib]# gcc -o pow_test pow_test.c -L. –lpow
在运行可执行程序之前,需要注册动态库的路径名。其方法有几种:修改/etc/ld.so.conf 文件,或者修
LD_LIBRARY_PATH环境变量,或者将库文件直接复制到/lib或者/usr/lib目录下(这两个目录为系统的
认的库路径名)。
[root@localhost lib]# cp libpow.so /lib
[root@localhost lib]# ./pow_test 2 10
2 ^ 10 = 1024 
动态库只有当使用它的程序执行时才被链接使用,而不是将需要的部分直接编译入可执行文件中,并且
个动态库可以被多个程序使用故可称为共享库,而静态库将会整合到程序中,因此在程序执行时不用加
静态库。  从而可知,链接到静态库会使用户的程序臃肿,并且难以升级,但是可能会比较容易部署。
链接到动态库会使用户的程序轻便,并且易于升级,但是会难以部署。
(3)告警和出错选项。
gcc的告警和出错选项如表3.9所示。
表3.9 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生成标准语法所要求的告警信息,尽管这还并不能保证所有没有警告的程序都是符合A
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还可以利用选项对单独的常见错误分别指定警告。
(4)优化选项。 

gcc可以对代码进行优化,它通过编译选项“-On”来控制优化代码的生成,其中n是一个代表优化级别的
整数。对于不同版本的gcc来讲,n的取值范围及其对应的优化效果可能并不完全相同,比较典型的范围
是从0变化到2或3。
不同的优化级别对应不同的优化处理工作。如使用优化选项“-O”主要进行线程跳转(Thread Jump)和延
迟退栈(Deferred Stack Pops)两种优化。使用优化选项“-O2”除了完成所有“-O1”级别的优化之外,同
时还要进行一些额外的调整工作,如处理器指令调度等。选项“-O3”则还包括循环展开和其他一些与处
理器特性相关的优化工作。
虽然优化选项可以加速代码的运行速度,但对于调试而言将是一个很大的挑战。因为代码在经过优化之后,
原先在源程序中声明和使用的变量很可能不再使用,控制流也可能会突然跳转到意外的地方,循环语句也
有可能因为循环展开而变得到处都有,所有这些对调试来讲都将是一场噩梦。所以笔者建议在调试的时候
最好不使用任何优化选项,只有当程序在最终发行的时候才考虑对其进行优化。
(5)体系结构相关选项。
gcc的体系结构相关选项如表3.10所示。
表3.10 gcc体系结构相关选项列表
选  项  含  义
-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返回,节省调用函数的一条指令
这些体系结构相关选项在嵌入式的设计中会有较多的应用,读者需根据不同体系结构将对应的选项进行组
合处理。在本书后面涉及具体实例时将会有针对性的讲解。
3.4 gdb调试器
调试是所有程序员都会面临的问题。如何提高程序员的调试效率,更好、更快地定位程序中的问题从而加
快程序开发的进度,是大家都很关注的问题。就如读者熟知的Windows下的一些调试工具,如Visual Studio
自带的设置断点、单步跟踪等,都受到了广大用户的赞赏。那么,在Linux下有什么很好的调试工具呢?
gdb调试器是一款GNU开发组织并发布的UNIX/Linux下的程序调试工具。虽然,它没有图形化的友好界
面,但是它强大的功能也足以与微软的Visual Studio等工具媲美。下面就请跟随笔者一步步学习gdb调试
器。
3.4.1 gdb使用流程
这里给出了一个短小的程序,由此带领读者熟悉gdb的使用流程。建议读者能够动手实际操作一下。
首先,打开Linux下的编辑器vi或者emacs,编辑如下代码(由于为了更好地熟悉gdb的操作,笔者在此
使用vi编辑,希望读者能够参见3.3节中对vi的介绍,并熟练使用vi)。
/*test.c*/ 
#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 %d\n", 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 { 
19 n += i; 
20 } 
21 printf("The sum of 1~m is = %d\n", n); 
20 } 
可以看出,gdb列出的源代码中明确地给出了对应的行号,这样就可以大大地方便代码的定位。
(2)设置断点。
设置断点是调试程序中一个非常重要的手段,它可以使程序运行到一定位置时暂停。因此,程序员在该位
置处可以方便地查看变量的值、堆栈情况等,从而找出代码的症结所在。
在gdb中设置断点非常简单,只需在“b”后加入对应的行号即可(这是最常用的方式,另外还有其他方
式设置断点),如下所示:
(gdb)b 6
Breakpoint 1 at 0x804846d: file test.c, line 6. 
要注意的是,在gdb中利用行号设置断点是指代码运行到对应行之前将其停止,如上例中,代码运行到第
6行之前暂停(并没有运行第6行)。
(3)查看断点情况。
在设置完断点之后,用户可以键入“info b”来查看设置断点情况,在gdb中可以设置多个断点。
(gdb) info b
Num Type Disp Enb Address What 
1 breakpoint keep y 0x0804846d in main at test.c:6 
用户在断点键入“backrace”(只输入“bt”即可)可以查到调用函数(堆栈)的情况,这个功能在程序调
试之中使用非常广泛,经常用于排除错误或者监视调用堆栈的情况。
(gdb) b 19
(gdb) c 
Breakpoin 2, sum(m=50) at test.c:19 
19 printf(“The sum of 1-m is %d\n”, n); 
(gdb) bt 
#0 sum(m=50) at test.c:19 /* 停在test.c的sum()函数,第19行*/ 
#1 0x080483e8 in main() at test.c:6/* test.c的第6行调用sum函数*/ 
(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”是在第4行赋值的,
故在此时已经为零。
小技巧
gdb在显示变量值时都会在对应值之前加上“$N”标记,它是当前变量值的引用
标记,所以以后若想再次引用此变量就可以直接写作“$N”,而无需写冗长的变
量名。
(6)单步运行。
单步运行可以使用命令“n”(next)或“s”(step),它们之间的区别在于:若有函数调用的时候,“s”会
进入该函数而“n”不会进入该函数。因此,“s”就类似于Uisual等工具中的“step in”,“n”类似与Uisual
等工具中的“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中,程序的运行状态有“运行”、“暂停”和“停止”3种,其中“暂停”
状态为程序遇到了断点或观察点之类的,程序暂时停止运行,而此时函数的地址、

函数参数、函数内的局部变量都会被压入“栈”(Stack)中。故在这种状态下可
以查看函数的变量值等各种属性。但在函数处于“停止”状态之后,“栈”就会
自动撤消,它也就无法查看各种信息了。
3.4.2 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中的命令进行相关的操作,其功能极其强大。gdb常见工作环境相关命令如表3.11所示。
表3.11 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.12所示。
表3.12 gdb设置断点与恢复相关命令
命 令 格 式  含  义
Info b  查看所设断点
break [文件名:]行号或函数名<条件表达式>  设置断点
tbreak [文件名:]行号或函数名<条件表达式>  设置临时断点,到达后被自动删除
delete [断点号] 
删除指定断点,其断点号为“info b”中的第一栏。若缺
省断点号则删除所有断点
disable [断点号] 
停止指定断点,使用“info b”仍能查看此断点。同delete
一样,若缺省断点号则停止所有断点
enable [断点号]  激活指定断点,即激活被disable停止的断点
condition [断点号] <条件表达式>  修改对应断点的条件
ignore [断点号]<num>  在程序执行中,忽略对应断点num次
Step  单步恢复程序运行,且进入函数调用
Next  单步恢复程序运行,但不进入函数调用
Finish  运行程序,直到当前函数完成返回
C  继续执行函数,直到函数结束或遇到新的断点
设置断点在gdb的调试中非常重要,下面着重讲解gdb中设置断点的方法。
gdb中设置断点有多种方式:其一是按行设置断点;另外还可以设置函数断点和条件断点。下面具体介绍
后两种设置断点的方法。
①  函数断点。
gdb中按函数设置断点只需把函数名列在命令“b”之后,如下所示:
(gdb) b test.c:sum (可以简化为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.13所示。
表3.13 gdb源码查看相关相关命令
命 令 格 式  含  义
list <行号>|<函数名>  查看指定位置代码
file [文件名]  加载指定文件
forward-search 正则表达式  源代码的前向搜索
reverse-search 正则表达式  源代码的后向搜索
dir DIR  将路径DIR添加到源文件搜索的路径的开头
show directories  显示源文件的当前搜索路径
info line  显示加载到gdb内存中的代码
4.gdb中查看运行数据相关命令 
gdb中查看运行数据是指当程序处于“运行”或“暂停”状态时,可以查看的变量及表达式的信息,其常
见命令如表3.14所示。
表3.14 gdb查看运行数据相关命令 

命 令 格 式  含  义
print 表达式|变量  查看程序运行时对应表达式和变量的值
x <n/f/u> 
查看内存变量内容。其中n为整数表示显示内存的长度,f表示显示
的格式,u表示从当前地址往后请求显示的字节数
display 表达式  设定在单步运行或其他情况中,自动显示的对应表达式的内容
backtrace  查看当前栈的情况,即可以查到哪些被调用的函数尚未返回
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”。
  只有在代码处于“运行”或“暂停”状态时才能查看变量值。
  设置断点后程序在指定行之前停止。
3.5 make工程管理器
到此为止,读者已经了解了如何在Linux下使用编辑器编写代码,如何使用gcc把代码编译成可执行文件,
还学习了如何使用gdb来调试程序,那么,所有的工作看似已经完成了,为什么还需要make这个工程管
理器呢?
所谓工程管理器,顾名思义,是用于管理较多的文件。读者可以试想一下,由成百上千个文件构成的项目,
如果其中只有一个或少数几个文件进行了修改,按照之前所学的gcc编译工具,就不得不把这所有的文件
重新编译一遍,因为编译器并不知道哪些文件是最近更新的,而只知道需要包含这些文件才能把源代码编
译成可执行文件,于是,程序员就不得不重新输入数目如此庞大的文件名以完成最后的编译工作。 

编译过程分为编译、汇编、链接阶段,其中编译阶段仅检查语法错误以及函数与变量是否被正确地声明了,
在链接阶段则主要完成函数链接和全局变量的链接。因此,那些没有改动的源代码根本不需要重新编译,
而只要把它们重新链接进去就可以了。所以,人们就希望有一个工程管理器能够自动识别更新了的文件代
码,而不需要重复输入冗长的命令行,这样,make工程管理器就应运而生了。
实际上,make工程管理器也就是个“自动编译管理器”,这里的“自动”是指它能够根据文件时间戳自动
发现更新过的文件而减少编译的工作量,同时,它通过读入makefile文件的内容来执行大量的编译工作。
用户只需编写一次简单的编译语句就可以了。它大大提高了实际项目的工作效率,而且几乎所有Linux下
的项目编程均会涉及它,希望读者能够认真学习本节内容。
3.5.1 makefile基本结构
makefile是make读入的惟一配置文件,因此本节的内容实际就是讲述makefile的编写规则。在一个makefile
中通常包含如下内容:
  需要由make工具创建的目标体(target),通常是目标文件或可执行文件;
  要创建的目标体所依赖的文件(dependency_file);
  创建每个目标体时需要运行的命令(command),这一行必须以制表符(tab键)开头。
它的格式为:
target: dependency_files 
command /* 该行必须以tab键开头*/ 
例如,有两个文件分别为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.omakefile 
可以看到,makefile执行了“hello.o”对应的命令语句,并生成了“hello.o”目标体。
注意
在makefile中的每一个command前必须有“Tab”符,否则在运行make命令时
会出错。
3.5.2 makefile变量
上面示例的makefile在实际中是几乎不存在的,因为它过于简单,仅包含两个文件和一个命令,在这种情
况下完全不必要编写makefile而只需在shell中直接输入即可,在实际中使用的makefile往往是包含很多的
文件和命令的,这也是makefile产生的原因。下面就可给出稍微复杂一些的makefile进行讲解。
david: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中有3个目标体(target),分别为david、kang.o和yul.o,其中第一个目标体的依赖文件
就是后两个目标体。如果用户使用命令“make david”,则make管理器就是找到david目标体开始执行。
这时,make会自动检查相关文件的时间戳。首先,在检查“kang.o”、“yul.o”和“david”3个文件的时间
戳之前,它会向下查找那些把“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”和“david”3 个文件,只要文件“kang.o”或“yul.o”中的至少有一个
文件的时间戳比“david”新,则第二行命令就会被执行。这样,make就完成了自动检查时间戳的工作,
开始执行编译工作。这也就是make工作的基本流程。
接下来,为了进一步简化编辑和维护makefile,make允许在makefile中创建和使用变量。变量是在makefile
中定义的名字,用来代替一个文本字符串,该文本字符串称为该变量的值。在具体要求下,这些值可以代
替目标体、依赖文件、命令以及makefile文件中其他部分。在makefile中的变量定义有两种方式:一种是
递归展开方式,另一种是简单方式。
递归展开方式定义的变量是在引用该变量时进行替换的,即如果该变量包含了对其他变量的引用,则在
引用该变量时一次性将内嵌的变量全部展开,虽然这种类型的变量能够很好地完成用户的指令,但是它
也有严重的缺点,如不能在变量后追加内容(因为语句:CFLAGS = $(CFLAGS) -O在变量扩展过程中可
能导致无穷循环)。
为了避免上述问题,简单扩展型变量的值在定义处展开,并且只展开一次,因此它不包含任何对其他变量
的引用,从而消除变量的嵌套引用。
递归展开方式的定义格式为:VA R = v a r。
简单扩展方式的定义格式为:VA R := v a r。
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 
david : $(OBJS) 
$(CC) $(OBJS) -o david 
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.15列出了makefile中常见预定义变量及
其部分默认值。
表3.15 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.16列出了makefile中常见的自动变量。
表3.16 makefile中常见的自动变量
自 动 变 量  含  义
$*  不包含扩展名的目标文件名称
$+ 
所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依
赖文件
$<  第一个依赖文件的名称
$?  所有时间戳比目标文件晚的依赖文件,并以空格分开
$@  目标文件的完整名称
$^  所有不重复的依赖文件,以空格分开
$%  如果目标是归档成员,则该变量表示目标的归档成员名称
自动变量的书写比较难记,但是在熟练了之后使用会非常方便,请读者结合下例中的自动变量改写的
makefile进行记忆。
OBJS = kang.o yul.o 

CC = gcc 
CFLAGS = -Wall -O -g 
david : $(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中定义了相同名称的变量,那么用户自定义变量将会覆盖同名的环境变量。
3.5.3 makefile规则
makefile的规则是make进行处理的依据,它包括了目标体、依赖文件及其之间的命令语句。在上面的例子中,
都显式地指出了makefile中的规则关系,如“$(CC) $(CFLAGS) -c $< -o $@”,但为了简化makefile的编写,
make还定义了隐式规则和模式规则,下面就分别对其进行讲解。
1.隐式规则 
隐含规则能够告诉make怎样使用传统的规则完成任务,这样,当用户使用它们时就不必详细指定编译的
具体细节,而只需把目标文件列出即可。make会自动搜索隐式规则目录来确定如何生成目标文件。如上
例就可以写成:
OBJS = kang.o yul.o 
CC = gcc 
CFLAGS = -Wall -O -g 
david : $(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.17给出了常见的隐式规则目录。
表3.17 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 
david : $(OBJS) 
$(CC) $^-o $@
%.o : %.c 
$(CC) $(CFLAGS) -c$<-o $@ 
3.5.4 make管理器的使用
使用make管理器非常简单,只需在make命令的后面键入目标名即可建立指定的目标,如果直接运行make,
则建立makefile中的第一个目标。
此外make还有丰富的命令行选项,可以完成各种不同的功能。表3.18列出了常用的make命令行选项。
表3.18 make的命令行选项
命 令 格 式  含  义
-C dir  读入指定目录下的makefile 
-f file  读入当前目录下的file文件作为makefile 
-I  忽略所有的命令执行错误
-I dir  指定被包含的makefile所在目录
-n  只打印要执行的命令,但不执行这些命令
-p  显示make变量数据库和隐含规则
-s  在执行命令时不显示命令
-w  如果make在执行过程中改变目录,则打印当前目录名
3.6  使用autotools 
在上一小节,读者已经了解到了make项目管理器的强大功能。的确,makefile可以帮助make完成它的使命,
但要承认的是,编写makefile确实不是一件轻松的事,尤其对于一个较大的项目而言更是如此。那么,有没有
一种轻松的手段生成makefile而同时又能让用户享受make的优越性呢?本节要讲的autotools系列工具正是为
此而设的,它只需用户输入简单的目标文件、依赖文件、文件目录等就可以轻松地生成makefile了,这无疑是
广大用户所希望的。另外,这些工具还可以完成系统配置信息的收集,从而可以方便地处理各种移植性的问题。
也正是基于此,现在Linux上的软件开发一般都用autotools来制作makefile,读者在后面的讲述中就会了解到。
3.6.1 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.scanhello.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 david 
#AC_INIT(FULL-PACKAGE-NAME,VERSION,BUG-REPORT-ADDRESS) 
AC_INIT(hello,1.0) 
# The next one is added by david 
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 所必备的宏,使automake 自动生成
makefile.in,也同前面一样,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 configureconfigure.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对自己发布的软件有严格的规范,
比如必须附带许可证声明文件COPYING等,否则automake执行时会报错。automake提供了3
种软件等级:foreign、gnu和gnits,让用户选择采用,默认等级为gnu。在本示例中采用foreign
等级,它只检测必须的文件。
bin_PROGRAMS定义要产生的执行文件名。如果要产生多个执行文件,每个文件名用空格隔开。 

hello_SOURCES定义“hello”这个执行程序所需要的原始文件。如果“hello”这个程序是由多个
原始文件所产生的,则必须把它所用到的所有原始文件都列出来,并用空格隔开。例如:若目标
体“hello”需要“hello.c”、“david.c”、“hello.h”三个依赖文件,则定义hello_SOURCES=hello.c david.c 
hello.h。要注意的是,如果要定义多个执行文件,则对每个执行程序都要定义相应的
file_SOURCES。
接下来可以使用automake命令来生成“configure.in”文件,在这里使用选项“-a”(或者“—adding-missing”)
可以让automake自动添加一些必需的脚本文件。如下所示:
[root@localhost automake]# automake –a(或者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.inhello.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就可以自动生成了。回忆整个步骤,用户不再需要定制不同的规则,而只需要输入简
单的文件及目录名即可,这样就大大方便了用户的使用。autotools生成makefile的流程如图3.9所示。 

3.6.2  使用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来制作的。


















你可能感兴趣的:(GCC编译器-<嵌入式Linux应用程序开发标准教程>-华清远见)