实验五:LINUX 下C语言使用、编译与调试实验
一、实验目的:
二、实验内容
三、实验步骤
1、文件编辑器vi
进入vi,直接执行vi编辑程序。
例:# vi test.c
显示器出现vi的编辑窗口,同时vi会将文件复制一份至缓冲区(buffer)。vi先对缓冲区的文件进行编辑,保留在磁盘中的文件则不变。编辑完成后,使用者可决定是否要取代原来旧有的文件。
1) vi的工作模式
(1)输入模式
输入以下命令即可进入vi输入模式:
a |
在光标之后加入资料 |
A |
在该行之末加入资料 |
i |
在光标之前加入资料 |
I |
在该行之首加入资料 |
o |
新增一行于该行之下 |
O |
新增一行于该行之上 |
(2)命令模式
在输入模式下,按ESC可切换到命令模式。命令模式下,可选用下列指令离开vi:
:q! |
离开vi,并放弃刚在缓冲区内编辑的内容 |
:wq |
将缓冲区内的资料写入磁盘中,并离开vi |
ZZ |
同wq |
:x |
同wq |
:w |
将缓冲区内的资料写入磁盘中,但并不离开vi |
:q |
离开vi,若文件被修改过,则要被要求确认是否放弃修改的内容,此指令可与:w配合使用 |
(3)命令模式下光标的移动
h |
左移一个字符 |
j |
下移一个字符 |
k |
上移一个字符 |
l |
右移一个字符 |
0(零) |
移至该行的行首 |
$ |
移至该行的行尾 |
^ |
移至该行的第一个字符处 |
H |
移至窗口的第一行 |
M |
移至窗口中间那一行 |
L |
移至窗口的最后一行 |
G |
移至该文件的最后一行 |
W, w |
下一个单词 (W 忽略符号) |
B, b |
上一个单词 (B 忽略符号) |
(4)命令模式下的编辑命令
dd |
删除当前光标所在行 |
yy |
复制当前光标所在行 |
p |
将复制的内容粘贴在光标所在的位置后 |
P |
将复制的内容粘贴在光标所在的位置前 |
x |
删除当前光标字符 |
X |
删除当前光标之前字符 |
u |
撤消 |
· |
重做 |
2、GNU C编译器
1)使用gcc
通常后跟一些选项和文件名来使用gcc编译器。gcc命令的基本用法如下:
gcc [options] [filenames]
命令行选项指定的编译过程中的具体操作
2) gcc常用选项
当不用任何选项编译一个程序时,gcc将建立(假定编译成功)一个名为a.out的可执行文件。
选项含义:
-o FILE 指定输出文件名,在编译为目标代码时,这一选项不是必须的。如果FILE 没
有指定,默认文件名是a.out.
例如,
# gcc test.c
编译成功后,当前目录下就产生了一个a.out文件。
也可用-o选项来为即将产生的可执行文件指定一个文件名来代替a.out。
例如:
#gcc –o count count.c
此时得到的可执行文件就不再是a.out,而是count。
-c GCC 仅把源代码编译为目标代码。默认时GCC 建立的目标代码文件有一个.o 的
扩展名。
-E 对文件进行预处理
-S 对文件进行编译,生成汇编代码。
-O 对源代码进行基本优化。这些优化在大多数情况下都会使程序执行得更快。
-g 在可执行程序中包含标准调试信息。
-Wall 允许发出GCC 能提供的所有有用的警告,也可以用-W(warning)来标识指定的
警告。
-l name 链接静态库
-L dir 库文件的搜索路径
3)执行文件
格式: ./可执行文件名
例:
# ./a.out
# ./count
4)示例程序
步骤1:设计编辑源程序代码
使用文本编辑器vim,在终端中输入:
[root@localhost root]#vim 1-1.c
输入完成后存盘:按ESC键→输入“:wq”回车
步骤2:编译程序
查看当前目录下是否有1-1.c文件,输入命令:
[root@localhost root]#ls
1-1.c
编译:
[root@localhost root]#gcc 1-1.c -o 1-1
步骤3:运行程序
[root@localhost root]#ls
1-1.c 1-1
[root@localhost root]#./1-1
这是第一个Linux c程序!
步骤1:编辑源程序代码
[root@localhost root]#vim 1-2.c
步骤2:用gcc编译程序
[root@localhost root]#gcc 1-2.c –o 1-2
步骤3:运行程序
[root@localhost root]#./1-2
步骤1:编辑源程序代码
[root@localhost root]#vim 1-3.c
步骤2:预处理阶段
[root@localhost root]#gcc 1-3.c –o 1-3.i –E
[root@localhost root]#vim 1-3.i
步骤3:编译阶段
[root@localhost root]#gcc 1-3.i –o 1-3.s –S
[root@localhost root]#vim 1-3.s
步骤4:汇编阶段
[root@localhost root]#gcc 1-3.s –o 1-3.o –c
步骤5:链接阶段
[root@localhost root]#gcc 1-3.o –o 1-3
Linux系统把printf和scanf函数的实现,都放在了libc.so.6的库文件中。在没有参数指定时,gcc到系统默认的路径“/usr/lib”下查找,链接到libc.so.6库函数中去,这样就有了printf和scanf函数的实现部分。把程序中一些函数的实现,这是链接阶段的工作。
完成链接后,gcc就可以生成可执行程序文件
步骤1:设计编辑源程序代码1-4.c
[root@localhost root]#vim 1-4.c
步骤2:设计编辑自定义的头文件my.h
[root@localhost root]#vim my.h
步骤3:正常编译1-4.c文件:
[root@localhost root]#gcc 1-4.c –o 1-4
编译器提示出错。
步骤4:加“-I dir”参数编译:
[root@localhost root]#gcc 1-4.c –o 1-4 –I /root
步骤1:编辑源程序代码
[root@localhost root]#vim 1-6.c
步骤2:用gcc编译程序
[root@localhost root]#gcc 1-6.c –o 1-6
结果发现编译器报错:
原因是需要指定函数的具体路径,要查找函数sin,输入:
[root@localhost root]#nm –o -D /lib/i386-linux-gnu/*.so | grep sin
在/lib/libm-2.3.2.so:00008610 W sin中除去函数库头lib及函数的版本号-2.3.2,所余下的符号为“m”,在编译时用字符“l”与余下的符号“m”相连接成“lm”,在编译时加上此参数就能正确地通过编译,即:
[root@localhost root]# gcc 1-6.c –o 1-6 -lm
[root@localhost root]#./1-6
1)调试编译代码
为了使gdb正常工作,必须使你的程序在编译时包含调试信息。调试信息里包含你程序里的每个变量的类型和在可执行文件里的地址映射以及源代码的行号。gdb利用这些信息使源代码和机器码相关联。
在编译时用–g 选项打开调试选项。
2) gdb基本命令
命 令 |
描 述 |
file |
装入欲调试的可执行文件 |
kill |
终止正在调试的程序 |
list |
列出产生执行文件的源代码部分 |
next |
执行一行源代码但不进入函数内部 |
step |
执行一行源代码并进入函数内部 |
run |
执行当前被调试的程序 |
quit |
终止gdb |
watch |
监视一个变量的值而不管它何时被改变 |
break |
在代码里设置断点,使程序执行到这里时被挂起 |
make |
不退出gdb就可以重新产生可执行文件 |
shell |
不离开gdb就执行UNIX shell 命令 |
3)使用gdb调试示例程序
设计一个程序,要求输入两个整数,判断并输出其中的最小数。
步骤1:编辑源程序代码:
步骤2:用gcc编译程序
[root@localhost root]#gcc 1-9.c –o 1-9 –g
步骤3:进入gdb调试环境
[root@localhost root]#gdb 1-9
步骤4:用gdb调试程序
(1)查看源文件
在gdb中输入“l”(list)就可以查看程序源代码,一次显示10行;
(2)设置断点
在gdb中设置断点命令是“b”(break),后面跟行号或者函数名。
如:(gdb) b 10
(3)查看断点信息
用命令“info b”(info break)查看断点信息。
(4)运行程序:输入“r”(run)开始运行程序。
(5)查看变量值
程序运行到断点处会自动暂停,输入“p 变量名”
调试程序时,可能需要修改变量值,程序运行到断点处时,输入“set 变量=设定值”,例如给变量“a2” 赋值11,输入“set a2=11”。
gdb在显示变量值时都会在对应值前加“$n”标记,它是当前变量值的引用标记,以后想再引用此变量,可以直接使用“$n”,提高了调试效率。
(6)单步运行
在断点处输入“n”(next)或者“s”(step) 。它们之间的区别在于:若有函数调用时,“s”会进入该函数而“n”不会进入该函数。
(7)继续运行程序
输入“c”(continue)命令恢复程序的正常运行,把剩余的程序执行完,并显示执行结果。
(8)退出gdb环境:输入“q”(quit)命令。
4)调试程序
(1)源程序gdbtest1.c,分析程序的功能,如果出错,要求用gdb进行调试并给出修改方案。
基本步骤:
输出结果不对:
gdb调试,发现i和sum都是静态的,在程序运行过程中每次调用add range函数i和sum的值不会改变,所以才会导致错误。
改进的程序:
(2) 源程序greet.c,功能:按照正序和逆序输出给定的字符串。要求用gdb进行调试,,分析出错的原因并给出修改方案。(没有发现错误))
4、make的使用
(1)用vi编辑以下程序,程序清单:
main.c
function1.h
function1.c
function2.h
function2.c
//main.c
#include "function1.h"
#include "function2.h"
int main(int argc, char **argv)
{
function1_print("hello");
function2_print("world");
return 0;
}
//function1.h
void function1_print(char *str);
//function1.c
#include "function1.h"
void function1_print(char *str)
{
printf("This is function1 print %s\n", str);
}
//function2.h
void function2_print(char *str);
//function2.c
#include "function2.h"
void function2_print(char *str)
{
printf("This is function2 print %s\n", str);
}
实验要求:
(2)编辑makefile文件
(3)利用make命令进行上述程序的编译,生成可执行代码并运行。
(4)修改其中一个源文件,重新make,察看编译过程。
(5)通过使用makefile变量和隐含规则,对makefile文件进行简化
实验题目:
对于下面的Makefile文件:
CC = gcc
OPTIONS = -O3 -o
OBJECTS = main.o stack.o misc.o
SOURCES = main.c stack.c misc.c
HEADERS = main.h stack.h misc.h
polish: main.c $(OPJECTS)
$(CC) $(OPTIONS) polish $(OBJECTS) -lm
main.o: main.c main.h misc.h
stack.o: stack.c stack.h misc.h
misc.o: misc.c misc.h
回答:
CC 、OPTIONS
b.所有目标文件的名字
每个目标文件的名字:main.o、stack.o、misc.o
c.每个目标的依赖文件
main.o: main.c main.h misc.h
stack.o: stack.c stack.h misc.h
misc.o: misc.c misc.h
d.生成每个目标文件所需执行的命令
cc -c main.c
cc -c stack.c
cc -c misc.c
e.画出makefile对应的依赖关系树。
f.生成main.o,为什么?
因为需要将编译后的代码变成汇编代码,生成main.o文件和Target有依赖关系,想要编译成功target必须生成main.o文件。
4.用编辑器创建main.c, compute.c, input.c, compute.h, input.h和main.h文件。下面是它们的内容。注意compute.h和input.h文件仅包含了compute和input函数的声明但没有定义。定义部分是在compute.c和input.c文件中。main.c包含的是两条显示给用户的提示信息。(相关程序代码如下)
[user@localhost user]$ cat compute.h
/*compute函数的声明原型*/
double compute(double,double);
[user@localhost user]$ cat input.h
/*input函数的声明原型*/
double input(char*);
[user@localhost user]$ cat main.h
/*声明用户提示*/
#define PROMPT1 "请输入x的值:"
#define PROMPT2 "请输入y的值:"
[user@localhost user]$ cat compute.c
#include
#include
#include "compute.h"
double compute(double x,double y)
{
return (pow((double)x,(double)y));
}
[user@localhost user]$cat input.c
#include
#include "input.h"
double input(char *s)
{
float x;
printf("%s",s);
scanf("%f",&x);
return (x);
}
[user@localhost user]$ cat main.c
#include
#include "main.h"
#include "compute.h"
#include "input.h"
int main(int argc, char *argv[])
{
double x,y;
printf("本程序从标准输入获取x和y的值并显示x的y次方.\n");
x=input(PROMPT1);
y=input(PROMPT2);
printf("x的y次方是:%6.3f\n",compute(x,y));
return 0;
}
结合上面代码创建Makefile文件,使用make命令,生成power可执行文件,并运行power程序。给出完成上述工作的步骤和程序运行结果。
#include
#define PROMPT "请输入一个整数:"
void get_input(char *, int *);
void main()
{
int *user_input;
get_input(PROMPT, user_input);
(void) printf("你输入了:%d。\n", user_input);
}
void get_input(char *prompt, int *ival)
{
(void) printf("%s", prompt);
scanf("%d", ival);
}
发现编译有错误:
修改程序:
还是输出结果不正确
心得体会:这次的实验有难度,花了很多精力和时间去了解gcc,make,gdb,首先gcc的内容很快就会做完,make内容是在老师上课演示一遍过程才知道怎么运行,gdb的话,我在阿里云开的服务器里虽然有gdb,但是怎么调都使用不了,自己又尝试安装了新的,但是一直不成功,最终我在自己旧电脑上用虚拟机做才成功,但是一直都不怎么理解gdb是怎么调试找出错误的,以至于有些用gdb调试的实验写得乱七八糟,最后一题,改了之后虽然编译成功但是结果却不正确,也是找了好久都找不出原因。