一、GCC
1) GCC可以为x86、ARM、MIPS等不同体系结构的硬件平台编译程序。
2) GCC可以编译C、C++、Pascal、Java等数十种高级语言。
GCC的这两项特性对嵌入式应用开发及其重要。此外,GCC的编译效率也是非常高的,一般要高出其他编译系统20%到30%左右。所以在嵌入式Linux开发领域,使用的基本上就是GCC编译系统。
gcc命令的使用格式为:
gcc [选项] [文件名] [选项] [文件名]
gcc命令拥有数量庞大的编译选项,按类型可以把选项分为以下几大类。
总体选项:用于控制编译的整个流程。
常用选项:
-c:对源文件进行编译或汇编。
-E:对源文件进行预处理。
-S:对源文件进行编译。
-ofile:输出目标文件file。
-v:显示编译阶段的命令。
语言选项:用于支持各种版本的C语言程序。
常用选项:
-ansi:支持符合ANSI标准的C程序。
警告选项:用于控制编译过程中产生的各种警告信息。
常用选项:
-W:屏蔽所有的警告信息。
-Wall:显示所有类型的警告信息。
-Werror:出现任何警告信息就停止编译。
调试选项:用于控制调试信息。
常用选项:
-g:产生调试信息。
优化选项:用于对目标文件进行优化。
常用选项:
-O1:对目标文件的性能进行优化。
-O2:在-O1的基础上进一步优化,提高目标文件的运行性能。
-O3:在-O2的基础上进一步优化,支持函数集成优化。
-O0:不进行优化。
连接器选项:用于控制链接过程。
常用选项:
-static:使用静态链接。
-llibrary:链接library函数库文件。
-Ldir:指定连接器的搜索目录dir。
-shared:生成共享文件。
目录选项:用于指定编译器的文件搜索目录。
常用选项:
-Idir:指定头文件的搜索目录dir。
-Ldir:指定搜索目录dir。
GCC工作流程
在C程序的编译过程中,依次要进行预处理、编译、汇编、链接四个阶段。
1. 预处理阶段
由于在test.c中使用了头文件stdio.h,所以GCC在编译时首先要把头文件stdio.h 中的内容加载到test.c中的首部。
在shell中输入命令“gcc -E test.c -o test.i”。其中,参数E告诉gcc命令只进行预编译,不做其他处理;参数o用来指明输出的文件名为test.i。命令运行完毕后就会产生一个名为test.i的文件。
编译阶段是整个编译过程中最复杂的一个阶段
1) 步骤1称为词法分析,主要负责检查关键字、标识符等是否正确。
2) 步骤2称为语法分析,主要负责检查程序中语句的语法是否正确。
3) 步骤3称为语义分析,主要负责检查程序中语句的逻辑意义是否正确。
在shell中输入命令“gcc -S test.i -o test.s”。其中,参数S告诉gcc命令只进行编译,不做其他处理。命令运行完毕后就会产生一个名为test.s的汇编文件。如下所示:
3. 汇编阶段
汇编阶段的任务是把汇编程序翻译成 CPU 可以识别的二进制文件,该文件又称为目标文件。
在shell中输入命令“gcc -c test.s -o test.o”,其中,参数c告诉gcc命令只进行汇编,不做其他处理。命令运行完毕后就会产生一个名为test.o的目标文件。
在Windows系统中,目标文件的后缀是obj。
4. 链接阶段
在shell 中输入命令“gcc test.o -o test”,运行完毕后就会产生一个名为 test 的可执行文件。输入命令“./test”执行该文件,就可以得到 test 文件的运行结果“Hello world!”。
gcc命令生成的可执行文件的有以下三种格式。
1)a.out(Assembler and Link editor output);
2) COFF(Commonobject file format);
3) ELF(Executableand linkable format);其中,a.out和COFF格式都是比较老的格式,现在Linux平台上可执行文件的主流格式是ELF。
二、Makefile
Make工程管理器是完全根据Makefile文件中的编译规则命令进行工作的。Makefile 文件由以下三项基本内容组成。
1) 需要生成的目标文件(target file)。
2) 生成目标文件所需要的依赖文件(dependency file)。
3) 生成目标文件的编译规则命令行(command)。
这三项内容按照如下格式进行组织:
targetfile :dependency file command
其中,Makefile规定在书写command命令行前必须加一个<Tab>键。
Make工程管理器在编译程序时会检查每个依赖文件的时间戳,一旦发现某个依赖文件的时间戳比目标文件要新,就会执行目标文件的规则命令来重新生成目标文件。这个过程称为目标文件的依赖规则检查。依赖规则检查是Make工程管理器的最核心的工作任务之一。
下面以编译程序test(由a.c、b.c和b.h组成)为例来描述Make的工作过程。例如:
a.c:
#include“b.h” int main()
{ hello(); return 0;
}
b.h:
voidhello();
b.c:
#include“stdio.h” void hello()
{ printf(“hello”);
}
Makefile:
test: a.o b.o cc -o test a.o b.o
a.o: a.c b.h cc -c a.c
b.o: b.c cc -c b.c
Make工程管理器编译test程序的过程如下:
(1) Make工程管理器首先会在当前目录下读取Makefile文件。
(2) 查找Makefile文件中的第一个目标文件(在本例中为test),该文件也是Make工程管理器本次编译任务的最终目标。
(3) 把目标文件test的依赖文件当作目标文件进行依赖规则检查。这是一个递归的检查过程。在本例中就是依次把a.o和b.o作为目标文件来检查各自的依赖规则。Make会根据以下三种情况进行处理:
1) 如果当前目录下没有或缺少依赖文件,则执行其规则命令生成依赖文件(比如缺少a. 文件,则执行命令“cc -c a.c”生成a.o)。
2) 如果存在依赖文件,则把其作为目标文件来检查依赖规则(假设a.c比a.o新,则执行命令“cc -ca.c”更新a.o)。
3) 如果目标文件比所有依赖文件新,则不做处理。
(4)递归执行第三步后,就会得到目标文件test所有最新的依赖文件了。接着Make会根据以下三种情况进行处理:
1) 如果目标文件test不存在(比如第一次编译),则执行规则命令生成test。
2) 如果目标文件test存在,但存在比test要新的依赖文件,则执行规则命令更新test。
3) 目标文件test存在,且比所有依赖文件新,则不做处理。
下面通过make的运行结果来印证上述流程。
(1)第一次编译时,由于没有 test、a.o 和 b.o,Make 会先执行命令“cc -c a.c”生成 a.o;然后接着执行命令“cc -c b.c”生成b.o;最后执行命令“cc -o test a.o b.o”生成test文件。
(2)如果修改了a.c文件,Make会先执行命令“cc-c a.c”生成a.o;由于b.o没有修改,所以Make就接着执行命令“cc -o test a.o b.o”生成test文件。
(3)如果删除了b.o文件,由于a.o没有修改,所以Make就先执行命令“cc -c b.c”生
成b.o;然后接着执行命令“cc -o test a.o b.o”生成test文件。
(4)如果再运行一次make时,因为所有的源文件都没有改动,所以Make不会有任何动作。
Makefile特性介绍
1. 变量
在Makefile文件中,存在着大量的文件名,而且这些文件名都是重复出现的。所以在源文件比较多的情况下,很容易发生遗漏或写错文件名。而且一旦源文件的名称发生了变化,还容易造成与其他文件名不一致的错误。于是,Makefile 提供了变量来代替文件名。变量的使用方式为:
$(变量名)
此外,Make工程管理器提供了灵活的变量定义方式,具体有以下几种实现方式。
(1) 通过“=”来实现例如:
a1=$(a2) a2= $(a3)
a3=a.o
这种方式下变量a1的值是a.o,也就是说前面的变量可以通过后面的变量来定义。但使用这种方式定义变量时,要防止出现死循环的情况。
(2) 通过“:=”来实现例如:
a1:=a.o
a2:=$(a1) b.o
这种方式下变量a1的值是a.o,变量a2的值是a.o b.o。
例如:
a1:=$(a2) b.o
a2:=a.o
这种方式下变量a1的值是b.o,而不是“a.ob.o”。也就是说前面的变量不能通过后面的变量来定义。
(3) 通过“+=”来实现例如:
a1=a.o
a1+=b.o
这种方式下变量a1的值是“a.o b.o”。也就是说“+=”可以实现给变量追加值。等同于如下示例:
a1=a.o
a1:=$(a1) b.o
可以看到,Makefile的“+=”和C语言中的“+=”是非常相似的。
(4) 通过“?=”来实现例如:
a1:=a.o
a1?=b.o
这种方式下变量a1的值是a.o,而不是b.o。也就是说,如果变量a1已经在前面定义过了,那么后面的定义就无效了。
以上所介绍的变量都是全局变量,也就是在整个Makefile文件中都可以访问的。
2. 自动推导
为了进一步简化Makefile的书写,Make工程管理器提供了自动推导的功能。自动推导功能默认每个目标文件都有一个与之对应的依赖文件。比如a.o文件有依赖文件a.c与之对应)。这样在Makefile中就不需要指定与目标文件对应的依赖文件名了。此外,自动推导功能还能推导出与目标文件对应的基本编译规则命令。比如a.o文件的规则命令为
“gcc–c –o a.c”。
例如:
obj= a.o b.o
test: $(obj)
cc-o test $(obj) a.o : b.h
结果为:
[root@localhosthome]#make cc –c –o a.o a.c cc –c –o b.o b.c
cc–o test a.o b.o
可以看到,Makefile分别推导出了目标文件a.o和b.o的规则命令“cc -c -o a.o a.c” 与“cc -c -o b.o b.c”。
3. 伪目标
伪目标不是真正的目标文件,所以通过伪目标可以让Make工程管理器只执行规则命令, 而不用创建实际的目标文件。伪目标的使用方式为:
make伪目标名由于伪目标不是真正的目标文件,只是一个符号。为了不和真实的目标文件混淆,最好使用“.PHONY”对伪目标进行标识。
(1) all
运行命令“make all”后,Make会把all看成是最终的目标。由于伪目标和真实目标
一样都有依赖文件,所以Make会更新all的依赖文件test、a.o和b.o。
(2) clean 运行命令“make clean”后,Make会执行命令“rm -rf test $(obj)”。这样test、a.o和b.o文件就全被删除了。
(3) install 运行命令“make clean”后,Make会顺序执行命令“mkdir $(test_dir)”和“cp test $(test_dir)”,把test文件复制到test_dir变量指定的目录中去(这里只是模拟安装过程,并不是真正的实现安装方法)。
(4) uninstall
运行命令“make clean”后,Make会执行命令“rm -rf $(test_dir)”。这样就可以把变量test_dir指定的目录以及目录中的文件全部删除。
在Makefile文件中,伪目标是非常有用的。比如在递归编译、并行编译等场合中, 使用伪目标可以方便地控制编译过程。
4. 文件查找
为了便于管理和组织,程序的源文件都根据功能的不同放置在不同的子目录中。但是源文件被分散存储之后,Makefile又如何才能找到这些源文件呢?Makefile提供了以下两种方法。
(1) VPATH
VPATH是一个特殊变量。Make在当前路径找不到源文件的情况下就会自动到VPATH中指定的路径中去寻找。VPATH的使用方法为:
VPATH= 目录 : 目录 „
(2) vpath
和VPATH不同的是,vpath并不是变量而是关键字。其作用和VPATH类似,但使用方式更加灵活。vpath的使用方法为:
vpath模式 目录: 目录 „
Make会在当前路径找不到文件时按照顺序依次查找/a和/b目录中所有的C文件。vpath 也可以对不同的路径采用不同的搜素模式。
Make会在当前路径找不到源文件时先查找/a目录下的C文件,然后再查找/b目录下的头文件。
5. 嵌套执行
如果把所有源文件的编译规则命令都写在一个Makefile中,会造成Makefile过于臃肿,为编写和修改带来了很大的不便。解决这个问题的办法是把 Makefile 分解成多个子 Makefile,并放置到程序的每个子目录中,每个子Makefile文件负责所在目录下源文件的编译工作。
Make工程管理器会首先读取程序根目录下的Makefile文件(总控Makefile),然后再去读取各个目录中的子Makefile文件。这个过程就称为Make的嵌套执行。
嵌套执行的使用方法为:
cd 子目录 && $(MAKE) 或:
$(MAKE)–c子目录
在使用嵌套编译时,上层Makefile把编译任务下发给各个下层Makefile进行处理。就好比公司的总经理管理部门经理,再由部门经理去管理每个员工。
总控Makefile中的变量可以通过“export 变量”的方式传递到各级子Makefile中,但不会覆盖子Makefile中的变量。也可以通过“unexport 变量”的方式不让变量传递到各级子Makefile中。
6. 条件判断和C语言的条件编译类似,Make工程管理器也可以在运行时对条件进行判断,然后进入条件分支继续编译。条件判断的书写格式如下所示:
条件表达式
如果真执行的文本段 endif
或者:
条件表达式
如果真执行的文本段 else
如果假执行的文本段 endif
条件表达式有以下四种格式:
ifeq(参数1,参数2)
作用:比较参数1和参数2的值是否相同,相同为真,相异为假
ifneq(参数1,参数2)
作用:比较参数1和参数2的值是否相同,相异为真,相同为假
ifdef(参数)
作用:参数非空为真,空为假
ifndef(参数)
作用:参数空为真,非空为假例如:
a1=a.o a2= b.o ifeq ($(a1), $(a2)) a1=x.o
elsea2=y.o
变量a1的值是a.o, 变量a2的值是y.o。
7. 函数
对于编程语言来说,函数的作用是非常重要的。为此,Make 工程管理器也引入了函数机制,以丰富Make控制编译过程的方法。和变量一样,函数也用符号$进行标识,其使用格式为:
$(函数名参数,参数„)
其中函数名和参数之间用空格隔开,参数与参数之间用逗号隔开。下面简单介绍一些常用的基本函数。
subst
格式:$(subset 参数1,参数2,参数3)
功能:把参数3中的参数1替换成参数2 返回值:被替换后的参数3
patsubst
格式:$(patsubset模式参数,参数1,参数2)
功能:把参数2中符合模式参数的单词(单词是指参数中被空格隔开的字符串)替换成参数1 返回值:被替换后的参数2
wildcard
格式:$(wildcard模式参数)
功能:列出当前目录下所有符合模式参数的文件名返回值:当前目录下所有符合模式参数的文件名
strip参数
格式:$(strip 参数)
功能:去掉参数中开头和结尾的空格返回值:被去掉空格的参数
findstring
格式:$(findstring 参数1,参数2)
功能:在参数2中查找参数1 返回值:如果找到返回参数1,如果没找到返回空
filter
格式:$(filter模式参数,参数1)
功能:从参数1中筛选出符合模式参数的字符串返回值:符合参数模式的字符串
addsuffix
格式:$(addsuffix 参数1,参数2)
功能:在参数2中的每个单词加上后缀参数1 返回值:加上后缀的所有单词
addprefix
格式:$(addprefix 参数1,参数2)
功能:在参数2中的每个单词加上前缀参数1 返回值:加上前缀的所有单词
foreach
格式:$(foreach 变量参数,参数1,表达式)
功能:循环取出参数1中的单词赋给变量参数,然后运行表达式返回值:表达式的运行结果
call
格式:$(call 变量参数,参数„)
功能:循环把参数依次赋给变量参数中的$(1)、$(2)„ 返回值:赋值后的变量值
if
格式:$(if 条件参数,执行参数)
功能:如果条件参数非空,运行执行参数部分返回值:条件参数非空,返回执行参数部分
例如: 。
格式:$(if 条件参数,执行参数1,执行参数2)
功能:如果条件参数非空,运行执行参数1部分;反之运行执行参数2部分返回值:条件参数非空,返回执行参数1;反之返回执行参数2
dir
格式:$(dir 参数)
功能:从参数中取出目录部分返回值:目录部分
error
格式:$(error 参数)
功能:停止“Make”运行并显示参数返回值:参数
warning
格式:$(warning 参数)
功能:“Make”运行时显示参数返回值:参数
4.3Makefile的自动生成
使用Make 工程管理器虽然能大幅提高编译和调试程序的效率,但 Makefile 文件的编
写仍然是件非常麻烦的事。那么可不可以自动生成Makefile文件呢?
在Linux平台上广泛使用autotools工具来实现Makefile文件的自动生成。Autotools 是由一系列工具组成的,每个工具完成一个阶段的任务,最后生成一个完整的Makefile文件。下面以4.1.1节中的test.c文件为例来介绍Autotools工具自动生成Makefile的过程(为避免干扰,把test.c文件放在/home/auto目录下)。
(1)autoscan autoscan会在当前的目录中搜索源文件并生成autoscan.log和configure.scan两个文件。其中,autoscan.log是日志文件。configure. scan是一种脚本配置文件,里面定义了一些宏定义(“#”开头的是注释)。宏定义的意义如下所示:
AC_PREREQ :声明要求的autoconf版本,本文件的版本是“2.59”。
AC_INIT :声明要生成程序的基本信息(包的全称、版本号以及发送出错信息的邮箱)。
AC_CONFIG_SRCDIR :检查指定的源文件和目录是否存在与有效。
AC_CONFIG_HEADER :用于生成config.h文件。
AC_PROG_CC :用于确定C编译器。 AC_PROG_MAKE_SET :用于确定Make的设置。此外,还需要人工添加以下两个宏定义:
AM_INIT_AUTOMAKE :声明要生成程序的基本信息(程序名称和版本号,本例默认为“1.0”)。
AC_CONFIG_FILES :用于生成相应的Makefile文件。如下所示的是修改后的configure.scan源代码。
# -*- Autoconf -*- # Process this file withautoconf to produce a configure script.
AC_PREREQ(2.59)
AC_INIT(FULL-PACKAGE-NAME,VERSION, BUG-REPORT-ADDRESS)
AM_INIT_AUTOMAKE(test,1.0)
AC_CONFIG_SRCDIR([test.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
最后把修改完的configure.scan文件重命名为configure.in。
(2)aclocal
运行aclocal 会生成 aclocal.m4 文件和autom4te.cache 文件夹。主要用来处理
configure.in中的宏定义。
(3)autoconf 运行autoconf会生成configure文件。
(4)autoheader 运行autoheader会生成config.h.in文件。
(5)automake
运行automake前,需要先创建Makefile.am文件。在Makefile.am文件中需要定义以下三个宏定义:
AUTOMAKE_OPTIONS :用于设置automake的选项。automake共提供了三种软件等级,分别是foreign、gnu和gnits,默认等级为gnu。本例使用foreign等级,该级别只检测必须的文件。
bin_PROGRAMS :声明要产生的二进制文件名。如果要产生多个二进制文件,每个文件名用空格隔开。本例中的二进制文件名为test。
test_SOURCES :声明产生test文件所需要的源文件。如果有多个源文件,则必须把所有的源文件都列出来,并用空格隔开。
本例Makefile.am文件的内容如下所示:
AUTOMAKE_OPTIONS=foreignbin_PROGRAMS=test test_SOURCES=test.c
运行命令“automake --add-missing”,其中选项“--add-missing”可以让 automake 自动添加一些所需的脚本文件。该命令最终会生成Makfile.in文件。
(6)configure 运行命令“./configure”,该命令会把Makefile.in文件转变成Makefile文件。
由于该Makefile文件包含了目标文件的安装、删除、打包等功能,所以在结构、内容上是比较复杂的。最后运行命令“make”检验自动生成的Makefile能否正常工作。
三 、GDB调试器
test.c:
#include<stdio.h> int cal(int n)
{ if(n == 1) return 1;
else return n * cal(n - 1);
} int main()
{ int n = 5; n = cal(n); printf(“%d”,n); return 0;
}
test.c文件是一个通过递归调用来计算5的阶乘的程序。通过运行命令“gcc –g test.c -o test”对test.c进行编译,其中参数g的作用是把调试信息加入生成的test可执行文件中,否则GDB就无法对test进行调试。
接下来可以使用命令“gdb test”启动GDB对test进行调试了。
进行介绍。
(1)查看源文件在调试程序时,gcc会给出产生警告或错误的代码行数。但在普通的文本环境中是无法直接获得语句行数的。在GDB中通过命令l(list的缩写)可以查看所有的代码行数。
(2)设置断点断点是调试程序的重要方法,通过断点可以知道程序每一步的执行状况(比如当前变量的值、函数是否调用、堆栈使用情况等)。在GDB中通过命令b(breakpoint的缩写)进行断点设置。
(3)查看断点情况由于使用命令 b 可以设置多个断点,所以用户需要能够随时查看各个断点的情况。GDB中通过命令“info b”查看所有的断点情况。
(4)运行程序在GDB中通过命令r(run的缩写)运行程序。GDB默认从代码的首行开始运行(也可以通过“r 行数”的方式让程序从指定行数开始运行)。如果程序中有断点,则程序会在断点行数的前一行暂停运行。
(5)查看变量值程序暂停运行后就可以查看当前的状态了。在GDB中通过命令“p 变量名”(print的缩写)查看当前变量n的值。
(6)继续运行程序查看完当前程序的情况后,就可以让程序继续往下运行了。在GDB中通过命令c让程序继续往下运行。在test.c中,由于函数cal是递归调用运行,所以程序会再次在断点处暂停。
(7)单步运行在程序逻辑比较复杂的时候往往需要程序能一步一步的往下运行,但如果每行都设置一个断点的话又会很麻烦。在GDB中可以通过命令s(step的缩写)和n(next的缩写)让程序一步一步的往下运行。其中s可以在发生函数调用时进入函数内部运行,而n不会进入函数内部运行。在test.c中。由于函数cal是递归调用运行,所以只能选择s才能看到变量 n的值。