工程管理器make和调试工具gdb

来源:互联网 发布:dior fix it colour 编辑:程序博客网 时间:2024/05/16 11:06

Make

在实际的开发过程中,仅仅通过使用 gcc 命令对程序进行编译是非常低效的。源文件的个数越多,那么 gcc 的命令行就会越长。gcc 会把那些没有被修改的源文件一起编译,这样

就会影响编译的总体效率。所以引入工程管理器make。所有的编译规则都保存在 Makefile 文件中。全自动化的工程管理器在编译程序前会自动生成 Makefile 文件。

make的优点:

1)使用方便

 通过命令“make”就可以启动 Make 工程管理器对程序进行编译,所以不再需要每次都输入 gcc 命令行。Make 启动后会根据 Makefile 文件中的编译规则命令自动对源文件进行编译和链接,最终生成可执行文件。

 

2)调试效率高

 为了提高编译程序的效率,Make 会检查每个源文件的修改时间(时间戳)。只有在上次编译之后被修改的源文件才会在接下来的编译过程中被编译和链接,这样就能避免多余的编译工作量。

Make 工程管理器是完全根据 Makefile 文件中的编译规则命令进行工作的。Makefile 文件由以下三项基本内容组成。

 

1)需要生成的目标文件(target file)。

 

2)生成目标文件所需要的依赖文件(dependency file)。

 

3)生成目标文件的编译规则命令行(command)。

 

这三项内容按照如下格式进行组织:

 

target:dependency

<tab字符>command

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 存在,且比所有依赖文件新,则不做处理。



ep:

在makefile目录下编写makefile文件


CC=gcc

target = hello

object = hello.o print.o


$(target):$(object)

<tab>$(CC) $(object) -o $(target)

clean:

<tab>rm-rf helllo *.o


保存推出后输入make


ep:make

gcc -c -o print.o print.c

gcc hello.o print.o -o hello


输入ls

ep:ls

hello hello.c hello.o makefile print.c print.o


输入make clean后ls

ep: make clean

ls


hello. c makefile print.c


在 Makefile 文件中,存在着大量的文件名,而且这些文件名都是重复出现的。所以在源文件比较多的情况下,很容易发生遗漏或写错文件名。而且一旦源文件的名称发生了变

化,还容易造成与其他文件名不一致的错误。于是,Makefile 提供了变量来代替文件名。变量的使用方式为:$(变量名)


Make 工程管理器提供了灵活的变量定义方式,具体有以下几种实现方式。

1)通过“=”来实现

这种方式下前面的变量可以通过后面的变量来定义。但使用这种方式定义变量时,要防止出现死循环的情况。

2)通过“:=”来实现

这种方式下前面的变量不能通过后面的变量来定义。

3)通过“+=”来实现

这种方式下“+=”可以实现给变量追加值。

4)通过“?=”来实现

这种方式下如果变量 a1 已经在前面定义过了,那么后面的定义就无效了。


伪目标

 

伪目标不是真正的目标文件,所以通过伪目标可以让 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 又如何才能找到这些源文件呢?Makefile 提供了以下两种方法。

 

1)VPATH

 

VPATH 是一个特殊变量。Make 在当前路径找不到源文件的情况下就会自动到 VPATH 中指定的路径中去寻找。VPATH 的使用方法为:

 

 VPATH = 目录 : 目录 „

 

 

2)vpath

 

VPATH 不同的是,vpath 并不是变量而是关键字。其作用和 VPATH 类似,但使用方式更加灵活。vpath 的使用方法为:

 

 vpath 模式目录: 目录 „



嵌套执行

 

如果把所有源文件的编译规则命令都写在一个 Makefile 中,会造成 Makefile 过于臃肿,为编写和修改带来了很大的不便。解决这个问题的办法是把 Makefile 分解成多个子Makefile,并放置到程序的每个子目录中,每个子 Makefile 文件负责所在目录下源文件的编译工作。

 Make 工程管理器会首先读取程序根目录下的 Makefile 文件(总控 Makefile),然后再去读取各个目录中的子 Makefile 文件。这个过程就称为 Make 的嵌套执行。




gdb

程序的调试工作在整个程序的开发过程中占据了相当大的比例。使用 gcc 调试 C 程序时,只能依靠 gcc 发出的警告或错误信息来进行,所以调试的效率非常低。为此,GNU 开发了 GDB 调试器(GNU Debugger)。GDB 的调试功能非常强大,甚至可以 Visual C++、Visual Basic、Jbuilder 等开发工具的调试器相媲美。但 GDB 的缺点是没有图形调试界面。尽管如此,对于从事嵌入式 Linux 应用开发的人员还是有必要知道 GDB的使用方法的。

假设写个程序名为 add.c

1. 输入gcc -g add.c -o add 进行编译

2.使用命令 gdb add进行调试(若需参数,则gdb -- args add arg1 arg2 )

[root@localhost home]# gdb test

GNU gdb Everest Linux (6.4-1)

Copyright 2005 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 "i686-pc-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".

下面对一些常用命令进行介绍

1)查看源文件

 在调试程序时,gcc 会给出产生警告或错误的代码行数。但在普通的文本环境中是无法直接获得语句行数的。在 GDB 中通过命令 l(list 的缩写)可以查看所有的代码行数。GDB 以 10 行为单位进行显示。再运行一次命令 l 就会显示下 10 行代码。这样设计方便了源代码的阅读。


2)设置断点

 断点是调试程序的重要方法,通过断点可以知道程序每一步的执行状况(比如当前变量的值、函数是否调用、堆栈使用情况等)。在 GDB 中通过命令 b(breakpoint 的缩写)进行断点设置。如下所示:

 (gdb) b 7

 Breakpoint 1 at 0x8048389: file add.c, line 7.

可以看到,命令 b 在程序的第 7 行处设置了第一个断点,并显示了该断点在内存中的物理地址。


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的值。


















原创粉丝点击