gcc,gdb,make

GCC

在 Linux 平台上,最流行的编译系统是 GCC(GNU Compile Collection)。GCC 也是 GNU发布的最著名的软件之一。GCC 的功能非常强大,主要体现在两方面。

  1. GCC 可以为 x86、ARM、MIPS 等不同体系结构的硬件平台编译程序。
  2. GCC 可以编译 C、C++、Pascal、Java 等数十种高级语言。

GCC 的这两项特性对嵌入式应用开发及其重要。此外,GCC 的编译效率也是非常高的, 一般要高出其他编译系统 20%到 30%左右。所以在嵌入式 Linux 开发领域,使用的基本上就是 GCC 编译系统。

gcc 命令的使用格式为:

  gcc [选项] [文件名] [选项] [文件名]                                   

gcc 命令拥有数量庞大的编译选项,按类型可以把选项分为以下几大类。

 

1.总体选项:用于控制编译的整个流程。常用选项:

-c:对源文件进行编译或汇编。

-E:对源文件进行预处理。

-S:对源文件进行编译。

-o file:输出目标文件 file。

-v:显示编译阶段的命令。

2.语言选项:用于支持各种版本的 C 语言程序。常用选项:

-ansi:支持符合 ANSI 标准的C 程序。

3.警告选项:用于控制编译过程中产生的各种警告信息。常用选项:

-W:屏蔽所有的警告信息。

-Wall:显示所有类型的警告信息。

-Werror:出现任何警告信息就停止编译。


 

4.调试选项:用于控制调试信息。常用选项:

-g:产生调试信息。

5.优化选项:用于对目标文件进行优化。常用选项:

-O1:对目标文件的性能进行优化。

-O2:在-O1 的基础上进一步优化,提高目标文件的运行性能。

-O3:在-O2 的基础上进一步优化,支持函数集成优化。

-O0:不进行优化。

6.连接器选项:用于控制链接过程。常用选项:

-static:使用静态链接。

-llibrary:链接 library 函数库文件。

-L dir:指定连接器的搜索目录 dir

-shared:生成共享文件。

7.目录选项:用于指定编译器的文件搜索目录。常用选项:

-Idir:指定头文件的搜索目录 dir

-Ldir:指定搜索目录 dir。

此外,还有配置选项等其他选项,这里不做介绍了。

编译系统本身是一种相当复杂的程序,编写甚至读懂这样的程序都是非常困难的。但是从事嵌入式 Linux 应用的开发人员都应掌握编译系统的基本原理和工作流程

GCC 工作流程

在 C 程序的编译过程中,依次要进行预处理、编译、汇编、链接四个阶段。这里通过编译 C 文件 test.c 来展示 GCC 的工作流程。

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 的文件。

 

2.编译阶段

编译阶段是整个编译过程中最复杂的一个阶段。这里拿自然语言的翻译过程作个对比。比如在把“I love China”翻译成中文前,需要依次完成以下几个步骤:

  1. 考察这个句子中每个单词的拼写是不是正确。
  2. 考察整个句子的语法(比如主谓宾、定状补的结构等)是不是正确。
  3. 考察整个句子的语义是不是正确。

只有以上三个步骤都正常通过了,才能保证句子被正确翻译。同样,高级编程语言的编译阶段也必须实现这三个步骤。

  1. 步骤 1 称为词法分析,主要负责检查关键字、标识符等是否正确。
  2. 步骤 2 称为语法分析,主要负责检查程序中语句的语法是否正确。
  3. 步骤 3 称为语义分析,主要负责检查程序中语句的逻辑意义是否正确。

在 shell 中输入命令“gcc -S test.i -o test.s”。其中,参数 S 告诉 gcc 命令只进行编译,不做其他处理。命令运行完毕后就会产生一个名为 test.s 的汇编文件。

在学习使用汇编语言编程的时候,对照 C 文件和其汇编程序是很好的办法。如下所示的是 test.s 的代码。

3.汇编阶段

汇编阶段的任务是把汇编程序翻译成 CPU 可以识别的二进制文件,该文件又称为目标文件。

在 shell 中输入命令“gcc -c test.s -o test.o”,其中,参数 c 告诉 gcc 命令只进行汇编,不做其他处理。命令运行完毕后就会产生一个名为 test.o 的目标文件。在 Windows 系统中,目标文件的后缀是 obj。

3.链接阶段

目标文件虽然已经可以被 CPU 直接识别,但是单个目标文件一般是无法运行的。原因在于一个程序往往是由多个源文件组成的,每个源文件只对应一个目标文件。也许有人会问,

test 程序不就只有一个源文件 test.c 吗,为什么也不能直接运行呢?原因是 test.c 使用了 stdio.h 对应的库函数,所以必须要把 test.o 文件和函数库文件链接在一起才能运行。链接阶段的任务就是把程序中所有的目标文件和所需的库文件都链接在一起,最终生

成一个可以直接运行的文件,称为可执行文件。

在 shell 中输入命令“gcc test.o -o test”,运行完毕后就会产生一个名为 test 的可执行文件。输入命令“./test”执行该文件,就可以得到 test 文件的运行结果“Hello world!”。

gcc 命令生成的可执行文件的有以下三种格式。

1)a.out(Assembler and Link editor output);

2)COFF(Common object file format);

3)ELF(Executable and linkable format);

其中,a.out 和 COFF 格式都是比较老的格式,现在 Linux 平台上可执行文件的主流格式是 ELF。

GDB 调试器

程序的调试工作在整个程序的开发过程中占据了相当大的比例。使用 gcc 调试 C 程序

时,只能依靠 gcc 发出的警告或错误信息来进行,所以调试的效率非常低。

为此,GNU 开发了 GDB 调试器(GNU Debugger)。GDB 的调试功能非常强大,甚至可以

和 Visual C++、Visual Basic、Jbuilder 等开发工具的调试器相媲美。但 GDB 的缺点是没

有图形调试界面。尽管如此,对于从事嵌入式 Linux 应用开发的人员还是有必要知道 GDB

的使用方法的。
test.c 文件是一个通过递归调用来计算 5的阶乘的程序。通过运行命令“gcc –g test.c -o test”对 test.c 进行编译,其中参数 g 的作用是把调试信息加入生成的 test 可执行文件中,否则 GDB 就无法对 test 进行调试。接下来可以使用命令“gdb test”启动 GDB 对 test 进行调试了。

GDB 首先显示了版本信息和库信息。随后 GDB 停留在符号“(gdb)”处等待
用户输入调试命令。GDB 提供了大量的命令来实现各种调试功能,下面仅对一些常用的命令进行介绍。
(1)查看源文件
在调试程序时,gcc 会给出产生警告或错误的代码行数。但在普通的文本环境中是无法
直接获得语句行数的。在 GDB 中通过命令 l(list 的缩写)可以查看所有的代码行数。

GDB 以 10 行为单位进行显示。再运行一次命令 l 就会显示下 10 行代码。这样设计方便了源代码的阅读。
(2)设置断点
断点是调试程序的重要方法,通过断点可以知道程序每一步的执行状况(比如当前变量的值、函数是否调用、堆栈使用情况等)。在 GDB 中通过命令 b(breakpoint 的缩写)进行断点设置。
(3)查看断点情况
由于使用命令 b 可以设置多个断点,所以用户需要能够随时查看各个断点的情况。在GDB 中通过命令“info b”查看所有的断点情况。
(4)运行程序
在 GDB 中通过命令 r(run 的缩写)运行程序。GDB 默认从代码的首行开始运行(也可
以通过“r 行数”的方式让程序从指定行数开始运行)。如果程序中有断点,则程序会在断点行数的前一行暂停运行,结果如下所示:
(5)查看变量值
程序暂停运行后就可以查看当前的状态了。在 GDB 中通过命令“p 变量名”(print 的缩写)查看当前变量 n 的值。

  1. 继续运行程序
    查看完当前程序的情况后,就可以让程序继续往下运行了。在 GDB 中通过命令 c 让程序继续往下运行。在 test.c 中,由于函数 cal 是递归调用运行,所以程序会再次在断点处暂停。

(6)单步运行
在程序逻辑比较复杂的时候往往需要程序能一步一步的往下运行,但如果每行都设置一个断点的话又会很麻烦。在 GDB 中可以通过命令 s(step 的缩写)和 n(next 的缩写)让程序一步一步的往下运行。其中 s 可以在发生函数调用时进入函数内部运行,而 n 不会进入函数内部运行。在 test.c 中。由于函数 cal 是递归调用运行,所以只能选择 s 才能看到变量n 的值。

Makefile
Make 工程管理器是完全根据 Makefile 文件中的编译规则命令进行工作的。Makefile文件由以下三项基本内容组成。
1)需要生成的目标文件(target file)。
2)生成目标文件所需要的依赖文件(dependency file)。
3)生成目标文件的编译规则命令行(command)。
这三项内容按照如下格式进行组织:
target file :dependency file 
command 
其中,Makefile 规定在书写 command 命令行前必须加一个<Tab>键。
Make 工程管理器在编译程序时会检查每个依赖文件的时间戳,一旦发现某个依赖文件的时间戳比目标文件要新,就会执行目标文件的规则命令来重新生成目标文件。这个过程称为目标文件的依赖规则检查。依赖规则检查是 Make 工程管理器的最核心的工作任务之一。

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 -c a.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 文件。

(3)如果删除了 b.o 文件,由于 a.o 没有修改,所以 Make 就先执行命令“cc -c b.c”生
成 b.o;然后接着执行命令“cc -o test a.o b.o”生成 test 文件。

(4)如果再运行一次 make 时,因为所有的源文件都没有改动,所以 Make 不会有任何动作。

Makefile 特性介绍
源文件数量越是多的程序,其编译规则就会越复杂,导致 Makefile 文件也越复杂。为了简化 Makefile 的编写,丰富编译程序的方法和手段。Makefile 提供了很多类似高级编程语言的语法机制。
1. 变量
在 Makefile 文件中,存在着大量的文件名,而且这些文件名都是重复出现的。所以在
源文件比较多的情况下,很容易发生遗漏或写错文件名。而且一旦源文件的名称发生了变化,
还容易造成与其他文件名不一致的错误。于是,Makefile 提供了变量来代替文件名。变量
的使用方式为:
$(变量名) 
b.o : b.c
cc -c b.c
该 Makefile 使用了变量 obj 来代替“a.o b.o”。当源文件名发生改动或增删源文件时,只要对变量 obj 的值进行相应的修改就可以了,这样可以避免文件名不一致或遗漏的错误。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。
(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@localhost home]#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 工程管理器只执行规则命令,
而不用创建实际的目标文件。

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值