程序的编译与调试实验报告
实验二程序的编译与调试
第一部分配置本地计算机与虚拟机的通讯
一、配置本地计算机的vmnet1网络接口
1、 由开始→控制面→网络连接
2、 双击VMware Network Adapter VMnet1接口→在“VMware Network Adapter VMnet1”状态对话框
中选择属性按钮
3、 在“
”属性对话框中双击“Internet 协议(TCP/IP)”→出现“Internet
协议(TCP/IP
例:若VMnet1的IP 地址为:10.64.254.77
子网掩码为:255.255.255.0
那么:虚拟机中Linux 设置的IP 地址必须是:10.64.254.xx
其中xx 表示:1~254
二、配置虚拟机
1、 打开虚拟机,但不要执行Power on this virtual machine→而是双Network Adapter
2、 在出现的“Virtual Machine Setting”对话框中确保选中“Host-only ”选项→确定按钮
3、 选择“Power on this virtual machine”启动虚拟机
三、配置Linux 网络接口的IP 地址
1、使用root 用户登录linux 操作系统后,执行netconfig 命令,将linux 计算机IP 地址设置为与本地计算机的VMnet1网络接口的IP 地址处于同一网段,设置完成后输入reboot 重启linux 操作系统
例:若VMnet1的IP 地址为:10.64.254.77
子网掩码为:255.255.255.0
那么:虚拟机中Linux 设置的IP 地址必须是:10.64.254.xx
其中xx 表示:1~254
2、在本地计算由下载ssh_pshell,打开ssh_pshell连接虚拟机,以root 用户登录
第二部分 gcc 编译器的使用
作业:编译如下程序,并填写程序的输出结果:
1、源代码---Pointer.c
#include
int main()
{
int a = 100, b = 200;
int *p1 = &a, *p2 = &b;
printf(“%d, %d\n”,a,b);
printf(“%d,%d\n”,*p1, *p2);
printf(“%x,%x\n”,&a,&a);
printf(“%x,%x\n\n”,p1,p2);
*p1 = *p1*3;
Printf(“%d\n”,a);
Printf(“%d\n\n”,*p1);
p1 = &b;
printf(“%x\n”,p1);
printf(“%x\n”,p2);
return 0;
}
2、 编译:
#gcc –o pointer pointer.c
3、 执行
#./point
(二)、运行结果
0、vi 编辑器的使用 需要的技能
Vi 是广泛应用于各种unix 和Linux 系统中的文本编辑器,它是标准的Linux 文本编辑程序。它可打开或保存的格式均为ASCii 格式(文本)。
(一)、创建文件
格式:vi
⏹ 如果文件已经存在,vi 会打开现存文件
⏹ 如果是一个新文件,vi 会创建它
(二)、vi 编辑器的模式
⏹ 输入模式(插入模式)
⏹ 命令模式
由输入模式转入命令模式:ESC键
由命令模式转入输入模式:a、A ;o 、O ;i 、I 字符键
1、 输入模式
转入输入模式:
⏹ 添加:
⏹ 输入a 后,在光标的右边插入文本
⏹ 输入A ,在一行的结尾处添加文本
⏹ 插入:
⏹ 通过在命令模式下输入i ,在光标的左边插入文本
⏹ 通过在命令模式下输入I ,在行首插入文本
⏹ 插入新行:
⏹ 输入o ,在当前光标位置下面打开一行
⏹ 输入O ,在当前光标位置上面打开一行
2、 命令模式
在该模式中,可以输入命令来执行许多种功能
大多数的vi 命令都是由一个或两个字母加上一个可选数字组成
1)、命令模式可执行的命令
2)、翻页 ⏹ 向前翻页一屏:要向前滚动(向下移动)一整屏,需按下Ctrl-f 。光标将移动到新屏的左上角
⏹ 向前滚动半屏:要向前滚动半屏,需按Ctrl-d
⏹ 向后翻页一屏:要向后滚动(即向上移动)一整屏,需按下Ctrl-b
⏹ 向后滚动半屏:要向后滚动半屏,需按下Ctrl-u
3)、撤消更改 ⏹ 撤消前一个命令:
在最后一个命令之后立即输入u 来撤消该命令
⏹ 撤消对一行的更改:
输入U 来撤消你对一行所做的所有更改
这个命令只有在你没将光标移动到该行以外时才生效
4)、删除文本 ⏹ 删除一个字符:
⏹ 为删除一个字符,需将光标放置在要删除的字符上并输入x
⏹ 为删除光标之前(其左边)的一个字符,需输入X
⏹ 删除一个词或词的部分内容:
⏹ 为删除一个词,需将光标放置到该词的开头并输入dw
⏹ 为删除词的部分内容,需将光标放置到该词要保存部分的右边。输入dw 来删除该词余下的部分
⏹ 删除一行:
⏹ 将光标放置到该行的任意处并输入dd
⏹ 删除行的部分内容:
⏹ 将光标放置到该行要保存部分的右边,并输入D 。为删除光标左边的所有内容,须将光标放置到该
行要删除部分的右边,并输入d0(d-零)。
⏹ 删除到文件的结尾:
⏹ 为删除从当前行到文件结尾的所有内容,需输入dG
5)、复制和移动文本 ⏹ 复制一行命令:yy
⏹ 粘贴命令:p
⏹ 移动文本:
⏹ 先将要移动的部分用删除命令删除,然后再粘贴就可以了
3、查找字符串
1)、查找一个字符串: ⏹ 输入/,并在/后面输入要查找的串,然后按下回车
⏹ 输入“n”跳转到该串的下一个出现处
⏹ 输入“N”跳转到该串的上一个出现处
2)、替换一个字符串 ⏹ 在一行内替换头一个字符串old 为新的字符串new
⏹ :s/old/new
⏹ 在一行内替换所有的字符串old 为新的字符串new
⏹ :s/old/new/g
⏹ 在两行内替换所有的字符串old 为新的字符串new
⏹ :#,#s/old/new/g
⏹ 在文件内替换所有的字符串old 为新的字符串new
⏹ :%s/old/new/g
⏹ 进行全文替换时询问用户确认每个替换需添加c 选项
⏹ :%s/old/new/gc
一、程序的编译过程
在使用gcc 编译程序时,编译过程可以分为4个阶段
1)、预处理(Pre-Processing)
在预处理阶段,输入的是C 语言的源文件。在这个阶段主要处理源文件中的#indef、#include和#define预处理命令。该阶段会生成一个中间文件*.i,但实际工作中一般不用专门生成这个文件,若必须要生成这个文件,可以使用如下命令:
#gcc –E test.c -o test.i
说明:它通过对源文件test.c 使用E 选项来生成中间文件test.i
2)、汇编(Assembling)
在汇编阶段,输入的是中间文件*.i,编译后生成汇编编语言文件*.s。这个阶段对应的gcc 命令如下:
#gcc -S test.i -o test.s
3)、编译(Compiling)
在编译阶段,将输入的汇编文件*.s转换成二进制机器代码*.o,这个阶段对应的命令如下: #gcc -c test.s -o test.o
在功能上,预处理、编译、汇编是3个不同的阶段,但gcc 在实际操作时可以把这3个步骤合并为一个步骤来执行
4)、链接(Linking)
在链接阶段将输入的二进制机器代码*.o,与其它的机器代码和库文件汇集成一个可执行的二进制代码文件,这步骤通过以下命令来实现:
#gcc test.o -o test 最终生成可执行文件test
上述过程可以简化为:
#gcc -c test.c -o test.o
#gcc test.o -o test
或直接使用一条命令;
#gcc test.c -o test
二、gcc 常用选项
使用gcc 编译源程序时,源文件可能不止一个,这时就需要使用gcc 编译多个源文件,使用的命令如下:
#gcc -o test testmain.c other1.c other2.c
gcc 最基本的用法是:
格式: gcc [选项] [filenames]
选项:-c 只编译,不链接成可执行文件,编译器只是由输入的.c 等为后缀的源
代码文件生成.o 为后缀的目标文件,通常用于编译不包含主程序的子
程序文件
-o output_filename 确定输出文件的名称为output_filename,同时这个名称
不能和源文件同名。如果不给出这个选项,gcc 就默认
将输出的可执行文件命名为a.out
-g 产生调试器gdb 所必需的符号信息,要对源代码进行调试,就必须在
编译程序时加入这个选项
-O 对程序进行优化编译、链接,采用这个选项,整个源代码会在编译、
链接过程中进行优化处理,这样产生的可执行文件的执行效率较高,
但是,编译、链接的速度就相应地要慢一些
-O2 比-O 更好的优化编译、链接,当然整编译、链接过程会更慢
-Wall 输出所有警告信息,在编译过程中如果gcc 遇到一些认为可能会发生
错误的地方就会提出一些相应的警告和提示信息,提示注意这个地方
是不是会有什么错误
-w 关闭所有警告,建议不要使用这个选项
-I dirnme 将名为dirname 的目录加入到程序头文件目录列表中,它是在预
处理阶段使用的选项。I 意指Incude
在C 语言程序中,头文件被大量使用。一般而言,C 程序通常由头文件(header files) 和定义文件(definition files )组成。头文件一般包含函数原型说明、常量定义的文件,用于保存程序的声明,而定义文件用于保存程序的实现
C 程序中包含头文件有两种方法:
① #include
② #include “myinc.h ”
第一种使用尖括号(),第二种使用(“”)。
对于第一种,编译器gcc 在系统预设包含文件目录(如/usr/include)中查找相应的头文件myinc.h 。
而对于第二种,编译器gcc 首先在当前目录中查找头文件,如果当前目录中没有找到需要的头文件,就到指定的dirname 目录中去寻找。
对于系统提供的头文件,通常以第一种方式包含到源程序中。
在实际开发中,对于自己编写的头文件,通常放在与源程序相同的目录中,并以第二种方法包含该头文件。但在编写大型程序时,往往把头文件单独放在一个目录中,此时编译程序时需要用该选项告诉编译器到名为dirname 的目录中去查找头文件。否则,编译器会因为找不到相应的头文件而使编译失败。
-L dirname 将名为dirname 的目录加入到程序的库文件搜索目录列表中,它
在链接过程中使用的参数。L 意指Link
库是事先已经编写好的代码,经过编译后可以直接调用的文件。也可以编写属于自己的库文件,以方便自己使用或提供给他人使用。库体现了软件工程中的软件重用的思想。对于一些常用的功能,可以编写成一系统的函数,并把这些函数按功能放在库文件中。系统默认提供了许多库,比如数学函数库,它提供了一些求绝对值、开方、求三角函数值等功能函数。
库分为两种:静态库和动态库。
在默认情况下,编译器gcc 在系统默认路径中(如/usr/lib)寻找所需要的库文件,这个选项告诉编译器,首先到-L 指定的目录中去寻找,然后到系统默认的路径中寻找,如果函数库放在多个目录下,就需要依次使用这个选项,给出相应的存放目录。
-lname 指示编译器,在链接时,装载名为libname.a 的函数库,该函数库位
于系统预定义的目录或者由-L 选项指定的目录下,如-lm 表示链接
名为libm.a 的数学函数库
第三部分 gdb 编译器的使用
作业
1、程序1计算1~100的和,应该输出5050,但程序运行后输出值为:4950 # cat -n test.c 1 #include 2 3 int get_sum(int n) 4 { 5 int sum = 0,i; 6 for (i=0;i
# gcc -g test.c -o test # ./test 1+2+...+100=4950
试用gdb 调试器说明程序设计错误在何处
(gdb) break 7 if i==99
Breakpoint 5 at 0x80483aa: file test.c, line 7.
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home2/b5/test
Breakpoint 5, get_sum (n=100) at test.c:7
7 sum+=i;
(gdb) print i
$5 = 99
(gdb) print sum
$6 = 4851
(gdb) next
6 for(i=0;i
(gdb) next
8 return sum;
(gdb) print sum
$7 = 4950
(gdb) next
9 }
(gdb) next
main () at test.c:15
15 printf("1+2+...+%d=%d\n",i,result);
(gdb) next
1+2+...+100=4950
16 return 0;
(gdb) next
17 }
(gdb) next
0x4003a250 in __libc_start_main () from /lib/tls/libc.so.6
所以少加一个100
正确的完整代码为:
#include
int get_sum(int n)
{
int sum=0,i;
for(i=0;i
sum+=i;
return sum;
}
int main()
{
int i=100,result;
result=get_sum(i);
printf("1+2+...+%d=%d\n",i,result);
return 0;
}
2、程序2
在sort()函数中有两个循环。外层循环针对每个数组元素执行一次,它的循环计数变量是i 。内层循环的作用是交换相邻的两个元素。总的效果是让比较小的元素像“气泡”一样“冒”到数组的顶部。外层循环每执行一次,数组中的最大的元素就会“下沉”到数组的底部。我们可以通过在外层循环中停止此程序的运行并检查数组的状态来核实这一点
/* 1 */ typedef struct { /* 2 */ char data[4096]; /* 3 */ int key; /* 4 */ } item; /* 5 */ /* 6 */ item array[] = { /* 7 */ {"bill", 3}, /* 8 */ {"neil", 4}, /* 9 */ {"john", 2}, /* 10 */ {"rick", 5}, /* 11 */ {"alex", 1}, /* 12 */ }; /* 13 */ /* 14 */ sort(a,n) /* 15 */ item *a; /* 16 */ { /* 17 */ int i = 0, j = 0; /* 18 */ int s = 1; /* 19 */ /* 20 */ for(; i a[j+1].key) { /* 24 */ item t = a[j];
/* 25 */ a[j] = a[j+1]; /* 26 */ a[j+1] = t; /* 27 */ s++; /* 28 */ } /* 29 */ } /* 30 */ n--; /* 31 */ } /* 32 */ } /* 33 */ #include /* 34 */ main() /* 35 */ { /* 36 */ int i; /* 37 */ sort(array,5); /* 38 */ for(i = 0; i
array[1] = {alex, 1} array[2] = {bill, 3} array[3] = {neil, 4} array[4] = {rick, 5}
由运行结果可以看出,程序并未按从小到大的顺序排列,试说明原因:
(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
2: y array[0] @ 5
1: y array[0] @ 5
(gdb) info break
Num Type Disp Enb Address What
3 breakpoint keep y 0x080483cb in sort at test2.c:21
breakpoint already hit 3 times
cont
(gdb) disable display2
warning: bad breakpoint number at or near 'display2'
(gdb) disable display 2
(gdb) run
Starting program: /home2/b5/test2
Breakpoint 3, sort (a=0x80496a0, n=5) at test2.c:21
21 for(j = 0; j
1: array[0] @ 5 = {{data = "bill", '\0' , key = 3}, {
data = "neil", '\0' , key = 4}, {
data = "john", '\0' , key = 2}, {
data = "rick", '\0' , key = 5}, {
data = "alex", '\0' , key = 1}}
Breakpoint 3, sort (a=0x80496a0, n=4) at test2.c:21
21 for(j = 0; j
1: array[0] @ 5 = {{data = "bill", '\0' , key = 3}, {
data = "john", '\0' , key = 2}, {
data = "neil", '\0' , key = 4}, {
data = "alex", '\0' , key = 1}, {
data = "rick", '\0' , key = 5}}
Breakpoint 3, sort (a=0x80496a0, n=3) at test2.c:21
21 for(j = 0; j
1: array[0] @ 5 = {{data = "john", '\0' , key = 2}, {
data = "bill", '\0' , key = 3}, {
data = "alex", '\0' , key = 1}, {
data = "neil", '\0' , key = 4}, {
---Type to continue, or q to quit---return
data = "rick", '\0' , key = 5}}
array[0] = {john, 2}
array[1] = {alex, 1}
array[2] = {bill, 3}
array[3] = {neil, 4}
array[4] = {rick, 5}
Program exited with code 0224.
(gdb) disable break 3
(gdb) diaable display 2
Undefined command: "diaable". Try "help".
(gdb) disable display 1
(gdb) list 30
25 a[j+1] = t;
26 s++;
27 }
28 }
29 n--;
30 }
31 }
32 #include
33 main()
34 {
(gdb) break 29
Breakpoint 4 at 0x80484b2: file test2.c, line 29.
(gdb) commands 2
No breakpoint number 2.
(gdb) commands 4
Type commands for when breakpoint 4 is hit, one per line.
End with a line saying just "end".
>set variable n=n+1
>cont
>end
(gdb) run
Starting program: /home2/b5/test2
Breakpoint 4, sort (a=0x80496a0, n=5) at test2.c:29
29 n--;
Breakpoint 4, sort (a=0x80496a0, n=5) at test2.c:29
29 n--;
Breakpoint 4, sort (a=0x80496a0, n=5) at test2.c:29
29 n--;
Breakpoint 4, sort (a=0x80496a0, n=5) at test2.c:29
29 n--;
Breakpoint 4, sort (a=0x80496a0, n=5) at test2.c:29
29 n--;
array[0] = {alex, 1}
array[1] = {john, 2}
array[2] = {bill, 3}
array[3] = {neil, 4}
array[4] = {rick, 5}
A 、程序1的调试过程
在Linux 应用程序开发中,最常用的调试器是gdb ,它可以在程序中设置断点、查看变量值、一步一步跟踪程序的执行过程。利用调试器的这些功能可以方便地找出程序中存在的非语法错误。
一、启动和退出gdb
gdb 调试的对象是可执行文件,而不是程序的源代码。
如果要使一个可执行文件可以被gdb 调试,那么在使用编译器gcc 编译程序时需要加入-g 选项。-g 选项告诉gcc 在编译程序时加入调试信息,这样才可以调试这个被编译的程序。
例:计算1~100的和,应该输出5050
# cat -n test.c 1 #include 2 3 int get_sum(int n) 4 { 5 int sum = 0,i; 6 for (i=0;i
8 return sum; 9 } 10 11 int main() 12 { 13 int i = 100, result; 14 result = get_sum(i); 15 printf("1+2+...+%d=%d\n",i,result); 16 return 0; 17 } 编译并运行该程序:
# gcc -g test.c -o test # ./test 1+2+...+100=4950
(一)、调试一个程序的命令格式
格式:gdb
例:#gdb test
或 #gdb
(gdb) filetest
(二) 、退出
(gdb) quit
二、显示和查找程序的源代码
● ● ●
● ●
● ● 在调试时,一般要查看程序的源代码。list 命令用于列出程序的源代码,使用格式如下: list 显示10行代码,若再次运行该命令则显示接下来的10行代码 list 5, 10 显示源代文件test.c 中的第5行到第10行的代码, list test.c:5,10 显示源文件test.c 中第5行到第10行的代码,在调试含有多个源文件的程序时使用 list get_sum 显示get_sum函数周围的代码 list test.c:get_sum 显示源文件test.c 中get_sum函数周围的代码,在调试含多个源文件的程序时使用 search 用来从当前行向后查找第一个匹配的字符串 reverse-search 用来从当前行向前查找第一个匹配的字符串
三、执行程序和获得帮助
使用gdb test或(gdb) fiel test只是装入程序,程序并没有运行,如果要使程序运行,在gdb 提示符下输入run 即可。
(gdb) run Starting program: /tmp/test 1+2+...+100=4950 Program exited normally. 如果想要详细了解gdb 某个命令的使用方法,可以使用help 命令
(gdb)help list (gdb)help all
四、设置和管理断点
在调试程序时,往往需要程序在运行到某行、某个函数或某个条件发生时暂停下来,然后查看此时程序的状态,如各个变量的值、某个表达式的值等。为此,可以设置断点(break )。断点使程序运行到某个位置时暂停下来,以便检查和分析程序。
1、 以行号设置断点
在gdb 中,大部分都是使用break 命令为程序设置断点。而指定断点时,最常用的是为某行设置
断点。例:
(gdb) break 7 Breakpoint 1 at 0x80483c0: file test.c, line 7. 然后我们输入run 命令运行程序: (gdb) run Starting program: /tmp/test Breakpoint 1, get_sum (n=100) at test.c:7 7 sum +=i; 可以看到,程序运行完第6行的指令后就暂停了,第7行的代码并没有执行而是被gdb 的断点中断了。此时,我们可以查看各个变量和表达式的值,以了解程序的当前状态。
2、以函数名设置断点
在break 命令后跟上函数名,就可以为函数设置断点。例:
(gdb) break get_sum Breakpoint 1 at 0x80483aa: file test.c, line 5. (gdb) run Starting program: /tmp/test Breakpoint 1, get_sum (n=100) at test.c:5 5 int sum = 0,i; 可以看到,程序在第5行停了上来。
3、以条件表达式设置断点
程序在运行过程中,当某个条件满足时,程序在某行中断暂停执行
方法1 命令格式:break 行号或函数名 if 条件
例: (gdb) clear Deleted breakpoint 1 (gdb) break 7 if i==99 Breakpoint 2 at 0x80483c0: file test.c, line 7. (gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /tmp/test Breakpoint 2, get_sum (n=100) at test.c:7 7 sum +=i; 可以看到,运行程序后在i==99时,程序中断在第7行。
方法2 watch
例:(gdb) watch i==99
No symbol "i" in current context. (gdb) list 6 1 #include 2 3 int get_sum(int n) 4 { 5 int sum = 0,i; 6 for (i=0;i
为了解决这个问题,首先在第6行设置断点,然后使用run 命令运行程序,程序暂停在第6行,此时第5行的语句已经被执行,所以变量i 已经定义。这时就可以使用watch i==99设置断点了。
4、查看当前设置的断点
使用info breakpoints命令可以查看当前所有的中断点,例如:
(gdb) break 7 Breakpoint 1 at 0x80483c0: file test.c, line 7. (gdb) break 15 if result=5050 Breakpoint 2 at 0x8048405: file test.c, line 15. (gdb) info breakpoints Num Type Disp Enb Address What 1 breakpoint keep y 0x080483c0 in get_sum at test.c:7 2 breakpoint keep y 0x08048405 in main at test.c:15 stop only if result = 5050 其中:Num 表示断点的编号
Type 说明类型,类型为breakpoint 是指中断 Disp 指明中断点在生效一次后是否失去作用,是则为disp ,不是则为keep End 说明中断点是否有效,有效则为y ,无效则为n Address 列说明中断所处的内存地址 What 列列出中断发生在哪个函数的第几行 Stop only if result==5050 说明这是一个条件中断 5、使中断失效或有效
disable 可以使某个断点失效,程序运行到该断点时不会停下来而是继续运行 enable 可以使某个断点恢复有效 例:
(gdb) info breakpoints Num Type Disp Enb Address What 1 breakpoint keep y 0x080483c0 in get_sum at test.c:7 2 breakpoint keep y 0x08048405 in main at test.c:15 stop only if result = 5050 (gdb) disable 2 (gdb) info breakpoints Num Type Disp Enb Address What 1 breakpoint keep y 0x080483c0 in get_sum at test.c:7 2 breakpoint keep n 0x08048405 in main at test.c:15 stop only if result = 5050 (gdb) enable 2 (gdb) info breakpoints Num Type Disp Enb Address What 1 breakpoint keep y 0x080483c0 in get_sum at test.c:7 2 breakpoint keep y 0x08048405 in main at test.c:15 stop only if result = 5050 6、删除断点 ● clear 删除程序中所有的断点 ● clear 删除此行的断点 ● clear 删除该函数的断点 ● delete 删除指定编号的断点。如果一次要删除多个断点,各个断点编号以空格隔开 例:
(gdb) break 6 Breakpoint 1 at 0x80483b1: file test.c, line 6. (gdb) break 7 Breakpoint 2 at 0x80483c0: file test.c, line 7. (gdb) break 8 if sum==5050 Breakpoint 3 at 0x80483cf: file test.c, line 8. (gdb) info breakpoints Num Type Disp Enb Address What 1 breakpoint keep y 0x080483b1 in get_sum at test.c:6 2 breakpoint keep y 0x080483c0 in get_sum at test.c:7 3 breakpoint keep y 0x080483cf in get_sum at test.c:8 stop only if sum == 5050 (gdb) clear 6 Deleted breakpoint 1 (gdb) info breakpoints Num Type Disp Enb Address What 2 breakpoint keep y 0x080483c0 in get_sum at test.c:7 3 breakpoint keep y 0x080483cf in get_sum at test.c:8
stop only if sum == 5050 (gdb) delete 2 3 (gdb) info breakpoints No breakpoints or watchpoints. 7、display 在每次程序停在断点位置时,自动显示表达式中的内容 8、commands >命令 >end 结束命令输入 作用:指定在程序到达断点位置时需要执行的调试器命令
五、查看和设置变量的值
当程序执行到中断点暂停执行时,需要查看变量或表达式的值,借此了解程序的执行状态
1、print 命令
作用:print 命令一般用来打印变量或表达式的值,也可以用来打印内存中从某个变量开始的一段 存区域的内容,还可以用来对某个变量进行赋值。
格式:
print 打印变量或表达式的当前值,gdb 会用伪变量($n)来保存输出值以备用 Print ;对变量进行赋值 Print 打印以表达式值开始的n 个数 例:
(gdb) break 7 Breakpoint 4 at 0x80483c0: file test.c, line 7. (gdb) run Starting program: /tmp/test Breakpoint 4, get_sum (n=100) at test.c:7 7 sum += i; (gdb) print i
作用:用来显示某个变量或表达式值的数据类型
格式:whatis
例:
(gdb) break 7 Note: breakpoint 4 also set at pc 0x80483c0. Breakpoint 5 at 0x80483c0: file test.c, line 7. (gdb) run Starting program: /tmp/test Breakpoint 4, get_sum (n=100) at test.c:7 7 sum += i; (gdb) whatis i type = int (gdb) whatis sum+0.5 type = double 3、set 命令
作用:用来给变量赋值
格式:set variable 变量=值相当于print 变量=值
例:set variable i=200,相当于print i=200
六、控制程序的执行
当程序执行到指定的中断点,查看了变量或表达式的值后,可以让程序继续运行。也可以让程序一步一步地执行,或可以让程序地直运行下去直到下一个断点或运行完为止。
1、 continue 命令 作用:让程序继续运行,直到下一个断点或运行完为止。
格式:continue
2、 kill 命令 作用:用于结束当前程序的调试
例:(gdb) kill
Kill the program being debugged? (y or n) y 3、 next 和step 命令 作用:一次一条地执行程序代码
next 和step 的区别: 1)、如果遇到函数调用,next 会把该函数调用当作一条语句来执行,再次输入next 会执行函数调用 后的语句
2)、step 则会跟踪进入函数,一次一条地执行函数内的代码,直到函数的代码执行完,才执行函数调
用后的语句
例:next 语句的用法
(gdb) list 1,17 1 #include 2 3 int get_sum(int n) 4 { 5 int sum = 0,i; 6 for(i=0;i
(gdb) list 1,17
1 #include 2 3 int get_sum(int n) 4 { 5 int sum = 0,i; 6 for(i=0;i
注意:nexti 和next 类似,不会跟踪进入函数内部去执行
Stepi 和step 类似,跟踪进入函数执行。
B 、程序2调试过程
1. 设置断点 #gdb debug4 (gdb) break 22 Breakpoint 1 at 0x804841e: file debug4.c, line 22. 2. 设置到达断点时显示的内容 (gdb) display array[0]@5 3. 设置程序运行到达断点时,显示要查看的数据,然后继续执行 (gdb) commands Type commands for when breakpoint 1 is hit, one per line.
End with a line saying just "end". >cont >end 4、设置完成后运行程序 (gdb) run Starting program: /root/course/chapter10/debug4 Breakpoint 1, sort (a=0x8049800, n=5) at debug4.c:22 22 /* 22 */ for(j = 0; j , key = 3}, { data = "neil", '\0' , key = 4}, { data = "john", '\0' , key = 2}, { data = "rick", '\0' , key = 5}, { data = "alex", '\0' , key = 1}} Breakpoint 1, sort (a=0x8049800, n=4) at debug4.c:22 22 /* 22 */ for(j = 0; j , key = 3}, { data = "john", '\0' , key = 2}, { data = "neil", '\0' , key = 4}, { data = "alex", '\0' , key = 1}, { data = "rick", '\0' , key = 5}} Breakpoint 1, sort (a=0x8049800, n=3) at debug4.c:22 22 /* 22 */ for(j = 0; j , key = 2}, { data = "bill", '\0' , key = 3}, { data = "alex", '\0' , key = 1}, { data = "neil", '\0' , key = 4}, { data = "rick", '\0' , key = 5}} array[0] = {john, 2} array[1] = {alex, 1} array[2] = {bill, 3} array[3] = {neil, 4} array[4] = {rick, 5} Program exited with code 025. ---不常见的退出码,因为程序本身未调用exit 函数,并且也没有从main 函数返回一个值,该退出码没有实际意义。
5、用调试器打补丁
用调试器打补丁:通过将断点的设置与相应的操作结合起来来尝试修改程序,而不需要改变程序的源 码和重新编译。
针对debug4,需要在程序的第30行中断程序,增加变量n 的值,以使程序执行到第30行时,n 的值并未发生变化
重新开始执行这个程序,首先必须删除刚才设置的断点和display 命令的内容。可以用info 查看曾经设置过的断点及display 命令的内容
(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
1: y array[0]@5
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x08048417 in sort at debug4.c:21
breakpoint already hit 3 times
cont
禁用上述断点与display ,可以在今后必要的时候重新启用这些保留的设置
(gdb) disable break 1
(gdb) disable display 1
(gdb) break 30
Breakpoint 2 at 0x8048535: file debug4.c, line 30.
(gdb) commands 2
Type commands for when breakpoint 2 is hit, one per line.
End with a line saying just "end".
>set variable n=n+1
>cont
>end
(gdb) run
Starting program: /root/course/chapter10/debug4
Breakpoint 2, sort (a=0x8049800, n=5) at debug4.c:30
30 /* 30 */ n--;
Breakpoint 2, sort (a=0x8049800, n=5) at debug4.c:30
30 /* 30 */ n--;
Breakpoint 2, sort (a=0x8049800, n=5) at debug4.c:30
30 /* 30 */ n--;
Breakpoint 2, sort (a=0x8049800, n=5) at debug4.c:30
30 /* 30 */ n--;
Breakpoint 2, sort (a=0x8049800, n=5) at debug4.c:30
30 /* 30 */ n--;
array[0] = {alex, 1}
array[1] = {john, 2}
array[2] = {bill, 3}
array[3] = {neil, 4}
array[4] = {rick, 5}
Program exited with code 025.
第四部分编写程序
姓名:__________________ 学号:___________ 班级:____________ 成绩:________________
一、参考程序
使用异或计算使从标准输入(键盘)获得的文字,与&异或,变为密文后输出到标准输出设备(显示器),异或算法如下:
● 异或(XOR )操作(exclusive OR)
● 操作符可用XOR 或⊕表示
XOR 逻辑运算:当且仅当两个操作数中的一个为True 时,返回True ;
当奇偶校验RAID 产生校验和信息,它在每一个数据字节上执行XOR
参考程序
#include #include #define KEY '&' int main(void) { int orig_char, new_char; while((orig_char = getchar()) != EOF) { new_char = orig_char ^ KEY; if (isprint(orig_char) && isprint(new_char)) putchar(new_char); else putchar(orig_char); } return 0; } 程序运行结果:# ./xor
This a text string rNOU G RC^R URTOHA
二、与文件相关的标准I/O库函数
标准I/O库及其头文件stdio.h 为底层I/O系统调用提供了一个通用的接口。这个库现在已经成为ANSI 标准C 的一部分,标准I/O库提供了许多复杂的函数用于格式化输出和扫描输入。在标准I/O库中,与底层文件描述符对应的是流(stream ),它被实现为指向结构FILE 的指针。
1 fopen函数
作用:用于文件和终端的输入输出。
如果需要对设备进行明确的控制,最好使用底层系统调用,这样可以避免用库函数带来的一些潜在问题 函数原型:#include
FILE *fopen(const char *filename, const char *mode);
说明:fopen 打开由filename 参数指定的文件,并把它与一个文件流关联起来
mode 参数指定文件的打开方式,它取下列字符串中的值
“r ”或”rb ”以只读方式打开
“w ”或”wb ”以写方式打开,并把文件长度截短为0
“a ”或”ab ”以写方式打开,新内容追加在文件尾
“r+”或”rb+”或”r+b”以更新方式打开(读和写)
“w+”或”wb+”或”w+b”以更新方式打开,并把文件长短截短为0
“a+”或”ab+”或”a+b”以更新方式打开,新内容追加在文件尾
其中 b 指binary
“”双引号,因为参数都被看成是字符串,所以不能使用’’
返回值:fopen 在成功时返回一个非空的FILE*指针,
失败时返回NULL 值,NULL 值在头文件stdio.h 中定义
2 fread函数
作用:用于从一个文件流里读取数据。
数据从文件流stream 读到由ptr 指向的数据缓冲区里。
#include
Size_t fread(void *ptr, size_t size, size_t nitems, FILE *stream);
其中:size 参数指定每个数据记录的长度
nitems 计数器,给出要传输的记录个数(不是字符数)
3 fwrite函数
作用:从指定的缓冲区中取出数据,并把它们写到输出流中
#include
Size_t fwrite(const void *ptr, size_t size, size_t nitems, FILE *stream);
4 close函数
作用:关闭指定的文件流stream ,使所有尚未写出的数据都写出
#include
Int fclose(FILE *steam);
5 fgetc, getc, getchar函数
作用:从文件流里取出下一个字节,并把它作为一个字符返回。
当它到达文件尾或出现错误时,它返回EOF 。
函数原型:#include
Int fgetc(FILE *stream);
Int getc(FILE *stream);
Int getchar();
说明:getc 函数和fgetc 一样,但不能保证能够使用getc 的地址作为一个函数指针
getchar 函数的作用相当于getc(stdin),它从标准输入里读取一个字符
6 fputc, putc和putchar 函数
作用:fputc 函数把一个字符写到一个输出文件流中,它返回写入的值,如果失败,则返回EOF
函数原型:#include
Int fputc(int c, FILE *stream);
Int putc(int c, FILE *stream);
Int putchar(int c);
说明:把字符当作int 类型,而不是char 类型
7、标准I/O库函数示例:复制程序
1 #include
2 #include
3
4 int main()
5 {
6 int c;
7 FILE *in, *out;
8
9 in = fopen("file.in", "r");
10 out = fopen("file.out","w");
11
12 while((c=fgetc(in)) !=EOF)
13 fputc(c,out);
14
15 exit(0);
16 }
三、作业
有一源文件,文件名为:source.txt ,内容如下:
In recent years Linux has become a phenomenon. Hardly a day goes by without Linux cropping up in the media in some way. We’ve lost count of the number of applications that have been made available on Linux and the number of organizations that have adopted it, including some government departments and city administrations. Major hardware vendors like IBM and Dell now support Linux, and major software vendors like Oracle support their software running on Linux.
试参照“参考程序”,使用xor 运算,使上述文字变为密文存储于secret.txt 文件中。
程序清单:
#include
#include
#include
#define KEY '&'
int main(void)
{
int new_char,orig_char;
FILE *in,*out;
in=fopen("source.txt","r");
out=fopen("secret.txt","w");
while((orig_char = fgetc(in))!=EOF){ new_char = orig_char^KEY;
if(isprint(orig_char) && isprint(new_char)) fputc(new_char,out);
else
fputc(orig_char,out);
}
return 0;
}