1、项目九 使用gcc和make调试程序 项目导入:程序写好了,接下来做什么呢?调试!程序调程序写好了,接下来做什么呢?调试!程序调试对于程序员或管理员来说也是至关重要的一环。试对于程序员或管理员来说也是至关重要的一环。 职业能力目标和要求: 理解程序调试。理解程序调试。 掌握利用掌握利用gcc进行调试。进行调试。 掌握使用掌握使用make编译的方法。编译的方法。项目九项目九 使用使用gcc和和make调试程序调试程序 1.了解程序的调试 2.使用传统程序语言进行编译 3.使用 make 进行宏编译9.1 任务任务1 了解程序的调试 1 1. . 编译时错误编译时错误 2 2. . 运行时错误运行
2、时错误 3 3. . 逻辑错误和语义错误逻辑错误和语义错误子任务1 编译时错误 编译器只能翻译语法正确的程序,否则将导致编译失败,无法生成可执行文件。 虽然大部分情况下编译器给出的错误提示信息就是你出错的代码行,但也有个别时候编译器给出的错误提示信息帮助不大,甚至会误导你。在开始学习编程的前几个星期,你可能会花大量的时间来纠正语法错误。 等到有了一些经验之后,还是会犯这样的错误,不过会少得多,而且你能更快地发现错误原因。子任务2 运行时错误编译器检查不出这类错误,仍然可以生成可执行文件,但在运行时会出错而导致程序崩溃。读者在以后的学习中要时刻注意区分编译时和运行时(Run-time)这两个概念
3、,不仅在调试时需要区分这两个概念,在学习C语言的很多语法时都需要区分这两个概念,有些事情在编译时做,有些事情则在运行时做。子任务3 逻辑错误和语义错误 第三类错误是逻辑错误和语义错误。如果程序里有逻辑错误,编译和运行都会很顺利,看上去也不产生任何错误信息,但是程序没有干它该干的事情,而是干了别的事情。当然不管怎么样,计算机只会按你写的程序去做,问题在于你写的程序不是你真正想要的,这意味着程序的意思(即语义)是错的。找到逻辑错误在哪需要十分清醒的头脑,要通过观察程序的输出回过头来判断它到底在做什么。任务任务2 使用传统程序语言进行编译使用传统程序语言进行编译 1 1. . 安装安装gcc 2 2
4、. . 单一程序:打印单一程序:打印 Hello World 3 3. . 主程序、子程序链接、子程序的编译主程序、子程序链接、子程序的编译 4 4. . 调用外部函数库:加入链接的函数库调用外部函数库:加入链接的函数库 5 5. . gcc的简易用法(编译、参数与链接)的简易用法(编译、参数与链接)9.2.1 子任务1 安装gcc2安装安装gcc GCC(GNU Compiler Collection,GNU编译器集合),是一套由 GNU 开发的编程语言编译器。它是一套GNU编译器套装。以 GPL 许可证所发行的自由软件,也是 GNU计划的关键部分。GCC原本作为GNU操作系统的官方编译器,
5、现已被大多数类UNIX操作系统(如Linux、BSD、Mac OS X等)采纳为标准的编译器,GCC同样适用于微软的Windows。GCC是自由软件过程发展中的著名例子,由自由软件基金会以GPL协议发布。GCC 原名为 GNU C 语言编译器(GNU C Compiler),因为它原本只能处理 C语言。但GCC 后来得到扩展,变得既可以处理 C+,又可以处理 Fortran、Pascal、Objective-C、Java,以及 Ada与其他语言。9.2.1 子任务1 安装gcc1认识认识gcc (1)检查是否安装gcc。root rhel5 # rpm -qa|grep gcccompat-l
6、ibgcc-296-2.96-138libgcc-4.1.2-46.el5gcc-4.1.2-46.el5gcc-c+-4.1.2-46.el5表示已经安装了gcc。9.2.1 子任务1 安装gcc2安装安装gcc(2)如果没有安装。 挂载光驱。9.2.1 子任务1 安装gcc2安装安装gcc(2)如果没有安装。 制作用于安装的yum源文件9.2.1 子任务1 安装gcc2安装安装gcc 使用yum命令查看GCC软件包的信息,如图所示。9.2.1 子任务1 安装gcc2安装安装gcc 使用yum命令安装GCC。9.2.3 子任务3 主程序、子程序链接、子程序的编译1撰写所需要的主程序、子程序撰
7、写所需要的主程序、子程序rootwww # vim thanks.c#include int main(void) printf(Hello Worldn); thanks_2();# 上面的 thanks_2(); 那一行就是调用子程序! rootwww # vim thanks_2.c#include void thanks_2(void) printf(Thank you!n);9.2.3 子任务3 主程序、子程序链接、子程序的编译2进行程序的编译与链接(进行程序的编译与链接(Link)(1)开始将源码编译成为可执行的 binary file。rootwww # gcc -c thank
8、s.c thanks_2.crootwww # ll thanks*-rw-r-r- 1 root root 76 Jun 5 16:13 thanks_2.c-rw-r-r- 1 root root 856 Jun 5 16:13 thanks_2.o =编译生成的目标文件!-rw-r-r- 1 root root 92 Jun 5 16:11 thanks.c-rw-r-r- 1 root root 908 Jun 5 16:13 thanks.o =编译生成的目标文件!rootwww # gcc -o thanks thanks.o thanks_2.orootwww # ll than
9、ks*-rwxr-xr-x 1 root root 4870 Jun 5 16:17 thanks =最终结果会生成可执行文件9.2.3 子任务3 主程序、子程序链接、子程序的编译2进行程序的编译与链接(进行程序的编译与链接(Link)(2)执行可执行文件。rootwww # ./thanksHello WorldThank you!9.2.3 子任务3 主程序、子程序链接、子程序的编译2进行程序的编译与链接(进行程序的编译与链接(Link)此外,如果你想要让程序在运行的时候具有比较好的性能,或者是其他的调试功能时,可以在编译的过程里面加入适当的参数,例如下面的例子:rootwww # gcc
10、 -O -c thanks.c thanks_2.c = -O 为生成优化的参数rootwww # gcc -Wall -c thanks.c thanks_2.cthanks.c: In function main:thanks.c:5: warning: implicit declaration of function thanks_2thanks.c:6: warning: control reaches end of non-void function# -Wall 为产生更详细的编译过程信息。上面的信息为警告信息 (warning)# 所以不用理会也没有关系9.2.4 子任务子任务4
11、 调用外部函数库:加入链接的函数库调用外部函数库:加入链接的函数库 我们来写一个程序:rootwww # vim sin.c#include int main(void) float value; value = sin ( 3.14 / 2 ); printf(%fn,value);9.2.4 子任务子任务4 调用外部函数库:加入链接的函数库调用外部函数库:加入链接的函数库 那要如何编译这个程序呢?我们先直接编译:rootwww # gcc sin.csin.c: In function main:sin.c:5: warning: incompatible implicit declara
12、tion of built-in function sin/tmp/ccsfvijY.o: In function main:sin.c:(.text+0 x1b): undefined reference to sincollect2: ld returned 1 exit status# 注意看上面最后两行,有个错误信息,代表没有成功!9.2.4 子任务子任务4 调用外部函数库:加入链接的函数库调用外部函数库:加入链接的函数库 可以这样更正:编译时加入额外函数库链接的方式。rootwww # gcc sin.c -lm -L/lib -L/usr/lib =重点在 -lm rootwww
13、# ./a.out =尝试执行新文件1.0000009.2.4 子任务子任务4 调用外部函数库:加入链接的函数库调用外部函数库:加入链接的函数库 注意:由于 Linux 默认是将函数库放置在 /lib 与 /usr/lib 当中,所以你没有写 -L/lib 与 -L/usr/lib 也没有关系。不过,万一哪天你使用的函数库并非放置在这两个目录下,那么 -L/path 就很重要了,否则会找不到函数库的。9.2.4 子任务子任务4 调用外部函数库:加入链接的函数库调用外部函数库:加入链接的函数库 除了链接的函数库之外,你或许已经发现一个奇怪的地方,那就是在我们的 sin.c 当中第一行“ #inc
14、lude ”,这行说明的是要将一些定义数据由 stdio.h 这个文件读入,这包括 printf 的相关设置。这个文件其实是放置在 /usr/include/stdio.h 的。那么万一这个文件并非放置在这里呢?那么我们就可以使用下面的方式来定义要读取的 include 文件放置的目录。rootwww # gcc sin.c -lm -I/usr/include -I/path 后面接的路径(Path)就是设置要去寻找相关的 include 文件的目录。不过,同样,默认值是放置在 /usr/include 下面,除非你的 include 文件放置在其他路径,否则也可以略过这个选项。9.2.5
15、子任务子任务5 gcc的简易用法(编译、参数与链接)的简易用法(编译、参数与链接)下面我们就列举几个 gcc 常见的参数。# 仅将原始码编译成为目标文件,并不制作链接等功能rootwww # gcc -c hello.c# 会自动生成 hello.o 这个文件,但是并不会生成 binary 执行文件 # 在编译的时候,依据作业环境给予优化执行速度rootwww # gcc -O hello.c -c# 会自动生成 hello.o 这个文件,并且进行优化 9.2.5 子任务子任务5 gcc的简易用法(编译、参数与链接)的简易用法(编译、参数与链接)# 在进行 binary file 制作时,将链
16、接的函数库与相关的路径填入rootwww # gcc sin.c -lm -L/usr/lib -I/usr/include# 在最终链接成 binary file 的时候这个命令较常执行# -lm 指的是 libm.so 或 libm.a 这个函数库文件# -L 后面接的路径是刚刚上面那个函数库的搜索目录# -I 后面接的是源码内的 include 文件的所在目录9.2.5 子任务子任务5 gcc的简易用法(编译、参数与链接)的简易用法(编译、参数与链接)# 将编译的结果生成某个特定文件rootwww # gcc -o hello hello.c# -o 后面接的是要输出的 binary f
17、ile文件名 # 在编译的时候,输出较多的信息说明rootwww # gcc -o hello hello.c -Wall# 加入 -Wall 之后,程序的编译会变得较为严谨一点,所以警告信息也会显示出来9.3 任务任务3 使用使用 make 进行宏编译进行宏编译 1 1. .为什么要用为什么要用make 2 2. .了解了解makefile 的基本语法与变量的基本语法与变量9.3.1 子任务子任务1 为什么要用为什么要用make 先进行目标文件的编译,最终会有四个 *.o 的文件名出现。rootwww # gcc -c main.crootwww # gcc -c haha.crootwww
18、 # gcc -c sin_value.crootwww # gcc -c cos_value.c 再链接形成可执行文件main,并加入 libm 的数学函数,以生成 main 可执行文件。rootwww # gcc -o main main.o haha.o sin_value.o cos_value.o -lm -L/usr/lib -L/lib9.3.1 子任务子任务1 为什么要用为什么要用make 本程序的运行结果,必须输入姓名、360 度角的角度值来计算。rootwww # ./main Please input your name: Bobby 90): 30 =输入以 360 度
19、角为主的角度Hi, Dear Bobby, nice to meet you. =这三行这三行是是输出的结果输出的结果The Sin is: 0.50The Cos is: 0.879.3.1 子任务子任务1 为什么要用为什么要用make如果可以的话,能不能一个步骤就全部完成上面所有的操作呢?那就是利用 make 这个工具。先试着在这个目录下创建一个名为 makefile 的文件,代码如下。# 先编辑 makefile 这个规则文件,内容是制作出 main 这个可执行文件rootwww # vim makefilemain: main.o haha.o sin_value.o cos_valu
20、e.ogcc -o main main.o haha.o sin_value.o cos_value.o -lm# 注意:第二行的 gcc 之前是Tab按键产生的空格 9.3.1 子任务子任务1 为什么要用为什么要用make#. 尝试使用尝试使用 makefile 制订的规则进行编译制订的规则进行编译rootwww # rm -f main *.o =先将之前的目标文件删除rootwww # makecc -c -o main.o main.ccc -c -o haha.o haha.ccc -c -o sin_value.o sin_value.ccc -c -o cos_value.o c
21、os_value.cgcc -o main main.o haha.o sin_value.o cos_value.o -lm# 此时 make 会去读取 makefile 的内容,并根据内容直接去编译相关的文件 9.3.1 子任务子任务1 为什么要用为什么要用make# 在不删除任何文件的情况下,重新运行一次编译的动作rootwww # makemake: main is up to date.# 看到了吧!是否很方便呢?!只进行了更新 (update) 的操作9.3.2 子任务2 了解makefile 的基本语法与变量基本的 makefile 守则是这样的:目标(target): 目标文件
22、1 目标文件2 gcc -o 欲创建的可执行文件 目标文件1 目标文件2那该如何制作makefile文件呢?# 先编辑 makefile 来建立新的规则,此规则的目标名称为 clean rootwww # vim makefilemain: main.o haha.o sin_value.o cos_value.ogcc -o main main.o haha.o sin_value.o cos_value.o -lmclean:rm -f main main.o haha.o sin_value.o cos_value.o# 以新的目标(clean)测试,看看执行 make 的结果rootw
23、ww # make clean =就是这里!通过 make 以 clean 为目标rm -rf main main.o haha.o sin_value.o cos_value.o9.3.2 子任务2 了解makefile 的基本语法与变量如果想要先清除目标文件再编译 main 这个程序,就可以这样输入:“make clean main”,如下所示:rootwww # make clean mainrm -rf main main.o haha.o sin_value.o cos_value.occ -c -o main.o main.ccc -c -o haha.o haha.ccc -c
24、-o sin_value.o sin_value.ccc -c -o cos_value.o cos_value.cgcc -o main main.o haha.o sin_value.o cos_value.o -lm9.3.2 子任务2 了解makefile 的基本语法与变量可以再通过 shell script的“变量”来简化 makefile :rootwww # vim makefileLIBS = -lmOBJS = main.o haha.o sin_value.o cos_value.omain: $OBJS gcc -o main $OBJS $LIBSclean: rm -
25、f main $OBJS9.3.2 子任务2 了解makefile 的基本语法与变量例如:rootwww # CFLAGS=-Wall make clean main# 这个操作在 make上进行编译时,会取用 CFLAGS 的变量内容也可以这样:rootwww # vim makefileLIBS = -lmOBJS = main.o haha.o sin_value.o cos_value.oCFLAGS = -Wallmain: $OBJSgcc -o main $OBJS $LIBSclean:rm -f main $OBJS9.3.2 子任务2 了解makefile 的基本语法与变量所以也可以将 makefile 改成:rootwww # vim makefileLIBS = -lmOBJS = main.o haha.o sin_value.o cos_value.oCFLAGS = -Wallmain: $OBJSgcc -o $ $OBJS $LIBS =那个 $ 就是 main clean:rm -f main $OBJS9.3.2 子任务2 了解makefile 的基本语法与变量