(一)中已经介绍了使用strtok函数的一些注意事项,本篇将介绍strtok的一个应用并引出strtok_r函数。
1.一个应用实例
网络上一个比较经典的例子是将字符串切分,存入结构体中。如,现有结构体
typedef struct person{
char name[25];
char sex[10];
char age[4];
}Person;
需从字符串 char buffer[INFO_MAX_SZ]="Fred male 25,John male 62,Anna female 16"; 中提取出人名、性别以及年龄。
一种可行的思路是设置两层循环。外循环,先以 ',’ (逗号) 为分界符,将三个人的信息分开,然后对于每一个子串,再以 ' ’(空格) 为分界符分别得到人名、性别和年龄。
按照这个思路,理应能够实现所要的功能。为了简化步骤,我们调用strtok,先将子串先一一保存到字符串指针数组中,程序末尾打印指针数组中保存的所有子串,验证程序的正确性。得到的程序应该如下:
- int in=0;
- char buffer[INFO_MAX_SZ]="Fred male 25,John male 62,Anna female 16";
- char *p[20];
- char *buf = buffer;
- while((p[in]=strtok(buf,","))!=NULL)
- {
- buf=p[in];
- while((p[in]=strtok(buf," "))!=NULL)
- {
- in++;
- buf=NULL;
- }
- buf=NULL;
- }
- printf("Here we have %d strings/n", in);
- for (int j=0; j<in; j++)
- {
- printf(">%s</n",p[j]);
- }
执行的结果是,仅仅提取出了第一个人的信息。看来程序的执行并没有按照我们的预想。原因是什么?
原因是:在第一次外循环中,strtok将"Fred male 25,"后的这个逗号,改为了'/0’,这时strtok内部的this指针指向的是逗号的后一个字符'J’。经过第一次的内循环,分别提取出了“Fred” “male” “25”。提取完"25”之后,函数内部的this指针被修改指向了"25”后面的'/0’。内循环结束后(内循环实际执行了4次),开始第二次的外循环,由于函数第一个参数被设定为NULL,strtok将以this指针指向的位置作为分解起始位置。很遗憾,此时this指针指向的是'/0’,strtok对一个空串无法切分,返回NULL。外循环结束。所以,我们只得到了如图所示的第一个人的信息。
看来使用strtok并不能通过两层循环的办法,解决提取多人信息的问题。有没有其他办法呢? 显然,是有其他途径的。
我给出了一种解决办法。同时以 ',’ (逗号) 和 ' ’(空格) 为分界符,一层循环解决问题。
- in = 0;
- while ((p[in] = strtok(buf, " ,")) != NULL)
- {
- switch (in % 3)
- {
- case 0:
- printf("第%d个人:Name!/n", in/3+1);
- break;
- case 1:
- printf("第%d个人:Sex!/n", in/3+1);
- break;
- case 2:
- printf("第%d个人:Age!/n", in/3+1);
- break;
- }
- in++;
- buf = NULL;
- }
- printf("Here we have %d strings/n", in);
- for (int j=0; j<in; j++)
- {
- printf(">%s</n",p[j]);
- }

程序虽然可以达到理想的结果,但不是一个太好解决方案。程序要求你在提取之前必须要知道一个结构体中究竟包含了几个数据成员。明显不如双重循环那样直观。
倘若一定要采用二重循环那种结构提取,有没有合适的函数能够代替strtok呢? 有的,它就是strtok_r。
2.strtok_r及其使用
strtok_r是linux平台下的strtok函数的线程安全版。windows的string.h中并不包含它。要想使用这个函数,上网搜其linux下的实现源码,复制到你的程序中即可。别的方式应该也有,比如使用GNU C Library。我下载了GNU C Library,在其源代码中找到了strtok_r的实现代码,复制过来。可以看作是第一种方法和第二种方法的结合。
strtok的函数原型为 char *strtok_r(char *str, const char *delim, char **saveptr);
下面对strtok的英文说明摘自http://www.linuxhowtos.org/manpages/3/strtok_r.htm,译文是由我给出的。
The strtok_r() function is a reentrant version strtok(). The saveptr argument is a pointer to a char * variable that is used internally by strtok_r() in order to maintain context between successive calls that parse the same string.
strtok_r函数是strtok函数的可重入版本。char **saveptr参数是一个指向char *的指针变量,用来在strtok_r内部保存切分时的上下文,以应对连续调用分解相同源字符串。
On the first call to strtok_r(), str should point to the string to be parsed, and the value of saveptr is ignored. In subsequent calls, str should be NULL, and saveptr should be unchanged since the previous call.
第一次调用strtok_r时,str参数必须指向待提取的字符串,saveptr参数的值可以忽略。连续调用时,str赋值为NULL,saveptr为上次调用后返回的值,不要修改。
Different strings may be parsed concurrently using sequences of calls to strtok_r() that specify differentsaveptr arguments.
一系列不同的字符串可能会同时连续调用strtok_r进行提取,要为不同的调用传递不同的saveptr参数。
The strtok() function uses a static buffer while parsing, so it's not thread safe. Use strtok_r() if this matters to you.
strtok函数在提取字符串时使用了静态缓冲区,因此,它是线程不安全的。如果要顾及到线程的安全性,应该使用strtok_r。
strtok_r实际上就是将strtok内部隐式保存的this指针,以参数的形式与函数外部进行交互。由调用者进行传递、保存甚至是修改。需要调用者在连续切分相同源字符串时,除了将str参数赋值为NULL,还要传递上次切分时保存下的saveptr。
举个例子,还记得前文提到的提取结构体的例子么?我们可以使用strtok_r,以双重循环的形式提取出每个人的信息。
- int in=0;
- char buffer[INFO_MAX_SZ]="Fred male 25,John male 62,Anna female 16";
- char *p[20];
- char *buf=buffer;
- char *outer_ptr=NULL;
- char *inner_ptr=NULL;
- while((p[in] = strtok_r(buf, ",", &outer_ptr))!=NULL)
- {
- buf=p[in];
- while((p[in]=strtok_r(buf, " ", &inner_ptr))!=NULL)
- {
- in++;
- buf=NULL;
- }
- buf=NULL;
- }
- printf("Here we have %d strings/n",in);
- for (int j=0; j<in; j++)
- {
- printf(">%s</n",p[j]);
- }

调用strtok_r的代码比调用strtok的代码多了两个指针,outer_ptr和inner_ptr。outer_ptr用于标记每个人的提取位置,即外循环;inner_ptr用于标记每个人内部每项信息的提取位置,即内循环。具体过程如下:
(1)第1次外循环,outer_ptr忽略,对整个源串提取,提取出"Fred male 25",分隔符',' 被修改为了'/0’,outer_ptr返回指向'J’。
(2)第一次内循环,inner_ptr忽略,对第1次外循环的提取结果"Fred male 25"进行提取,提取出了"Fred",分隔符' '被修改为了'/0',inner_ptr返回指向'm'。
(3)第二次内循环,传递第一次内循环返回的inner_ptr,第一个参数为NULL,从inner_ptr指向的位置'm'开始提取,提取出了"male",分隔符 ' '被修改为了'/0',inner_ptr返回指向'2'。
(4)第三次内循环,传递第二次内循环返回的inner_ptr,第一个参数为NULL,从inner_ptr指向的位置'2'开始提取,提取出了"25",因为没有找到' ',inner_ptr返回指向25后的'/0'。
(5)第四次内循环,传递第三次内循环返回的inner_ptr,第一个参数为NULL,因为inner_ptr指向的位置为'/0',无法提取,返回空值。结束内循环。
(6)第2次外循环,传递第1次外循环返回的outer_ptr,第一个参数为NULL,从outer_ptr指向的位置'J'开始提取,提取出"John male 62",分隔符',’被修改为了'/0’,outer_ptr返回指向'A’。(调用strtok则卡死在了这一步)
……以此类推,外循环一次提取一个人的全部信息,内循环从外循环的提取结果中,二次提取个人单项信息。
可以看到strtok_r将原内部指针显示化,提供了saveptr这个参数。增加了函数的灵活性和安全性。
3.strtok和strtok_r的源代码
这两个函数的实现,由众多的版本。我strtok_r来自于GNU C Library,strtok则调用了strtok_r。因此先给出strtok_r的源代码。
- /*
- * strtok_r.c:
- * Implementation of strtok_r for systems which don't have it.
- *
- * This is taken from the GNU C library and is distributed under the terms of
- * the LGPL. See copyright notice below.
- *
- */
- #ifdef HAVE_CONFIG_H
- #include "configuration.h"
- #endif /* HAVE_CONFIG_H */
- #ifndef HAVE_STRTOK_R
- static const char rcsid[] = "$Id: strtok_r.c,v 1.1 2001/04/24 14:25:34 chris Exp $";
- #include <string.h>
- #undef strtok_r
- /* Parse S into tokens separated by characters in DELIM.
- If S is NULL, the saved pointer in SAVE_PTR is used as
- the next starting point. For example:
- char s[] = "-abc-=-def";
- char *sp;
- x = strtok_r(s, "-", &sp); // x = "abc", sp = "=-def"
- x = strtok_r(NULL, "-=", &sp); // x = "def", sp = NULL
- x = strtok_r(NULL, "=", &sp); // x = NULL
- // s = "abc/0-def/0"
- */
- char *strtok_r(char *s, const char *delim, char **save_ptr) {
- char *token;
- if (s == NULL) s = *save_ptr;
- /* Scan leading delimiters. */
- s += strspn(s, delim);
- if (*s == '/0')
- return NULL;
- /* Find the end of the token. */
- token = s;
- s = strpbrk(token, delim);
- if (s == NULL)
- /* This token finishes the string. */
- *save_ptr = strchr(token, '/0');
- else {
- /* Terminate the token and make *SAVE_PTR point past it. */
- *s = '/0';
- *save_ptr = s + 1;
- }
- return token;
- }
代码整体的流程如下:
(1)判断参数s是否为NULL,如果是NULL就以传递进来的save_ptr作为起始分解位置;若不是NULL,则以s开始切分。
(2)跳过待分解字符串开始的所有分界符。
(3)判断当前待分解的位置是否为'/0',若是则返回NULL(联系到(一)中所说对返回值为NULL的解释);不是则继续。
(4)保存当前的待分解串的指针token,调用strpbrk在token中找分界符:如果找不到,则将save_ptr赋值为待分解串尾部'/0'所在的位置,token没有发生变化;若找的到则将分界符所在位置赋值为'/0',token相当于被截断了(提取出来),save_ptr指向分界符的下一位。
(5)函数的最后(无论找到还是没找到)都将返回。
对于函数strtok来说,可以理解为用一个内部的静态变量将strtok_r中的save_ptr给保存起来,对调用者不可见。其代码如下:
- char *strtok(char *s, const char *delim)
- {
- static char *last;
- return strtok_r(s, delim, &last);
- }
有了上述两个函数的实现代码,再理解(一)(二)中所讲的一些要点也就不困难了。
花那么多篇幅总结这两个函数,一来是因为很对人对于strtok的误解比较深,网上很少有对于其非常详细的讨论,因此总结一份比较全面的材料,是有必要的;二来这也是自己不断学习的一个过程,总结会得到远比两个函数重要很多的信息。
strtok函数的使用是一个老生常谈的问题了。该函数的作用很大,争议也很大。以下的表述可能与一些资料有区别或者说与你原来的认识有差异,因此,我尽量以实验为证。交代一下实验环境是必要的,winxp+vc6.0,一个极端平民化的实验环境。本文中使用的源代码大部分来自于网络,我稍加修改作为例证。当然,本人水平有限,有不妥之处在所难免,各位见谅的同时不妨多做实验,以实验为证。
strtok的函数原型为char *strtok(char *s, char *delim),功能为“Parse S into tokens separated by characters in DELIM.If S is NULL, the saved pointer in SAVE_PTR is used as the next starting point. ” 翻译成汉语就是:作用于字符串s,以包含在delim中的字符为分界符,将s切分成一个个子串;如果,s为空值NULL,则函数保存的指针SAVE_PTR在下一次调用中将作为起始位置。
函数的返回值为从指向被分割的子串的指针。
这个定义和国内一些网站上的说法有一些差别,正是这些差别导致很多人对strtok没有一个正确的认识。希望读者在调用一些函数前,最好能够读一读官方的文档(多半都是英文的),而非看一些以讹传讹的资料。
使用strtok需要注意的有以下几点:
1.函数的作用是分解字符串,所谓分解,即没有生成新串,只是在s所指向的内容上做了些手脚而已。因此,源字符串s发生了变化!
设源字符串s为 char buffer[INFO_MAX_SZ]=",Fred male 25,John male 62,Anna female 16"; 过滤字符串delim为 char *delim = " ",即空格为分界符。
上图的代码会产生这样的结果:
首先,buffer发生了变化。如果此时打印buffer的值,会显示“,Fred”,而后面" male 25…16”不翼而飞了。实际上,strtok函数根据delim中的分界符,找到其首次出现的位置,即Fred后面那个空格(buffer[5]),将其修改成了'/0’。其余位置不变。这就很好解释为什么打印buffer的值只能出现“,Fred”,而非buffer中的全部内容了。因此,使用strtok时一定要慎重,以防止源字符串被修改。
理解了buffer的变化,就很好解释函数的返回值了。返回值buf为分界符之前的子串(其实这个说法并不确切,详见"3”中对于返回值的详细说明)。注意,由变量的地址可知,buf依然指向源字符串。
分界符delim没有发生变化,就不再截图了。
2.若要在第一次提取子串完毕之后,继续对源字符串s进行提取,应在其后(第二次,第三次。。。第n次)的调用中将strtok的第一个参数赋为空值NULL。
第一次调用的结果如前文所述,提取出了",Fred”。我们还想继续以空格为分界,提取出后面的"male”等。由上图可以看到,第一次之后的调用我们都给strtok的第一个参数传递了空值NULL(表示函数继续从上一次调用隐式保存的位置,继续分解字符串;对于上述的第二次调用来说,第一次调用结束前用一个this指针指向了分界符的下一位,即'm’所在的位置),这样可依次提取出
至于为什么要赋空值,要么你就记住结论,要么去查strtok的源代码。本文的最后会有一些介绍。
当然也有部分爱钻牛角尖的人,非不按套路出牌,要看看不赋空值继续赋值为buffer会有什么结果。其实,答案想也能想的到。再一次传递buffer,相当于还从字符串的开头查找分界符delim,而且此时buffer已经被修改(可见的部分只剩下",Fred”),因此,其结果必然是找不到分界符delim。
3.关于函数返回值的探讨
由"1”中所述,在提取到子串的情况下,strtok的返回值(假设返回值赋给了指针buf)是提取出的子串的指针。这个指针指向的是子串在源字符串中的起始位置。子串末尾的下一个字符在提取前为分隔符,提取后被修改成了'/0’。因此,若打印buf的值,可以成功的输出子串的内容。
在没有提取到子串的情况下,函数会返回什么值呢?
由上图可以看到buffer中并不包含分界符delim。调用strtok后buf的值为
因为没有找到,源字符串buffer没有发生改变,buf指向源字符串的首地址,打印输出的值为整个字符串的完整值。
什么时候函数的返回值为空值NULL呢?
百度百科上说,“当没有被分割的串时则返回NULL。”这是一个很模棱两可的说法。如果想要确切的了解清楚这个问题,可能需要看一下strtok的实现原理。这里先以实验说明。
第一次调用strtok,毫无疑问,buf指向",Fred”。
第二次调用strtok,由于第一个参数为NULL,表示函数继续以上次调用所保存的this指针的位置开始分解,即对"male 25”分解。分解完毕后,buf指向"male”。
第三次调用strtok,参数继续设定为NULL,此时即对第二次保存的this指针的位置开始分解,即对"25”分解。因为无法找到包含分隔符delim的子串,所以buf指向"25”。
第四次调用,参数仍为NULL,此时第三次调用保存的this指针已指向字符串的末尾'/0’,已无法再进行分解。因此函数返回NULL,这也就是百度百科中所提到的“当没有被分割的串时函数返回NULL。”
4.参数 分隔符delim的探讨(delim是分隔符的集合)
很多人在使用strtok的时候,都想当然的以为函数在分割字符串时完整匹配分隔符delim,比如delim=”ab”,则对于"acdab”这个字符串,函数提取出的是"acd”。至少我在第一次使用的时候也是这么认为的。其实我们都错了,我是在看函数的源代码时才发现这个问题的,且看下面的例子。
源字符串为buffer,分隔符delim为 逗号和空格,按照一般的想法我们会以为调用函数后,buf的值为"Fred,male,25”,结果是这样么?
第一次调用之后的结果竟然是"Fred”,而非我们所想的结果。这是为什么呢?
我们回到GNU C Library中对strtok的功能定义:“Parse S into tokens separated by characters in DELIM”。也就是说包含在delim中的字符均可以作为分隔符,而非严格匹配。可以把delim理解为分隔符的集合。这一点是非常重要的~
当然,我们在分解字符串的时候,很少使用多个分隔符。这也导致,很多人在写例子的时候只讨论了一个分隔符的情况。有更多的人在看例子的时候也就错误的认识了delim的作用。
5.待分解的字符串,首字符就为分隔符
首字符为分隔符不能算作一个很特殊的情况。按照常规的分解思路也能正确分解字符串。
我想说明的是,strtok对于这种情况采用了比常规处理更快的方式。
如上图例子所示。仅用一次调用就可以得到以逗号分隔的字符串"Fred male 25”,而F前面的','被忽略了。由此可见,strtok在调用的时候忽略了起始位置开始的分隔符。这一点,可以从strtok的源代码得到证实。
6.不能向第一个参数传递字符串常量!
本文中所举的例子都将源字符串保存为字符串数组变量。若你将源字符串定义成字符串常量,可想而知,程序会因为strtok函数试图修改源字符串的值,而抛出异常。
好了,本文详细介绍了使用strtok的注意事项,(二)中我将详细介绍strtok不能实现的一些功能并引出strtok_r函数,最后介绍一下两个函数的实现。
pipe.h
1 #ifndef _PIPE_H
2 #define _PIPE_H
3
4 int pipe_init(void); //初始化管道
5 int pipe_write_to_parent(void *buf, int len); //向父进程写数据
6 int pipe_write_to_child(void *buf, int len); //向子进程写数据
7 int pipe_read_from_parent(void *buf, int len); //读父进程数据
8 int pipe_read_from_child(void *buf, int len); //读子进程数据
9 void pipe_childend_close(void); //关闭子进程相应读、写端
10 void pipe_parentend_close(void); //关闭父进程相应读、写端
11 void pipe_free(void); //释放管道
12 #endif
pipe.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <sys/ioctl.h>
5 #include <errno.h>
6 #include <sys/types.h>
7
8 #include "pipe.h"
9
10 #define PIPE_READ_END 0 //预定义读端
11 #define PIPE_WRITE_END 1 //预定义写端
12
13 static int pipe_fd1[2]; //父进程向子进程,即读父进程、写子进程
14 static int pipe_fd2[2]; //子进程向父进程,即读子进程、写父进程
15 //创建管道
16 int pipe_init(void)
17 {
18 if (pipe(pipe_fd1) < 0 || pipe(pipe_fd2) < 0) {
19 return -1;
20 }
21
22 return 0;
23 }
24 //子进程向父进程写数据
25 int pipe_write_to_parent(void *buf, int len)
26 {
27 if (pipe_fd2[PIPE_WRITE_END] < 0 || buf == NULL) {
28 return -1;
29 }
30
31 if (write(pipe_fd2[PIPE_WRITE_END], buf, len) != len) {
32 return -1;
33 }
34
35 return 0;
36 }
37 //父进程向子进程写数据
38 int pipe_write_to_child(void *buf, int len)
39 {
40 if (pipe_fd1[PIPE_WRITE_END] < 0 || buf == NULL) {
41 return -1;
42 }
43
44 if (write(pipe_fd1[PIPE_WRITE_END], buf, len) != len) {
45 return -1;
46 }
47
48 return 0;
49 }
50 //读父进程管道pipe_fd1[PIPE_READ_END],并判断是否为指定长度len
51 int pipe_read_from_parent(void *buf, int len)
52 {
53 int rlen;
54 int ret;
55
56 if (pipe_fd1[PIPE_READ_END] < 0 || buf == NULL) {
57 return -1;
58 }
59
60 ioctl(pipe_fd1[PIPE_READ_END], FIONREAD, &rlen);
61 if (rlen < len) {
62 return -1;
63 } else {
64 ret = read(pipe_fd1[PIPE_READ_END], buf, len);
65 if (ret < 0) {
66 return -1;
67 }
68 }
69
70 return 0;
71
72 }
73 //读子进程管道pipe_fd2[PIPE_READ_END],并判断是否为指定长度len
74 int pipe_read_from_child(void *buf, int len)
75 {
76 int rlen;
77 int ret;
78
79 if (pipe_fd2[PIPE_READ_END] < 0 || buf == NULL) {
80 return -1;
81 }
82
83 ioctl(pipe_fd2[PIPE_READ_END], FIONREAD, &rlen);
84 if (rlen < len) {
85 return -1;
86 } else {
87 ret = read(pipe_fd2[PIPE_READ_END], buf, len);
88 if (ret < 0) {
89 return -1;
90 }
91 }
92
93 return 0;
94
95 }
96 //关闭子进程写端pipe_fd1[PIPE_WRITE_END],子进程读端pipe_fd2[PIPE_READ_END].
97 void pipe_childend_close(void)
98 {
99 if (pipe_fd1[PIPE_WRITE_END] > 0) {
100 close(pipe_fd1[PIPE_WRITE_END]);
101 pipe_fd1[PIPE_WRITE_END] = -1;
102 }
103
104 if (pipe_fd2[PIPE_READ_END] > 0) {
105 close(pipe_fd2[PIPE_READ_END]);
106 pipe_fd2[PIPE_READ_END] = -1;
107 }
108
109 }
110 //关闭父进程读端pipe_fd1[PIPE_READ_END],父进程写端pipe_fd2[PIPE_WRITE_END]
111 void pipe_parentend_close(void)
112 {
113 if (pipe_fd1[PIPE_READ_END] > 0) {
114 close(pipe_fd1[PIPE_READ_END]);
115 pipe_fd1[PIPE_READ_END] = -1;
116 }
117
118 if (pipe_fd2[PIPE_WRITE_END] > 0) {
119 close(pipe_fd2[PIPE_WRITE_END]);
120 pipe_fd2[PIPE_WRITE_END] = -1;
121 }
122
123 }
124 //关闭父子进程管道pipe_fd1,pipe_fd2
125 void pipe_free(void)
126 {
127 if (pipe_fd1[PIPE_READ_END] > 0) {
128 close(pipe_fd1[PIPE_READ_END]);
129 pipe_fd1[PIPE_READ_END] = -1;
130 }
131
132 if (pipe_fd1[PIPE_WRITE_END] > 0) {
133 close(pipe_fd1[PIPE_WRITE_END]);
134 pipe_fd1[PIPE_WRITE_END] = -1;
135 }
136
137 if (pipe_fd2[PIPE_READ_END] > 0) {
138 close(pipe_fd2[PIPE_READ_END]);
139 pipe_fd2[PIPE_READ_END] = -1;
140 }
141
142 if (pipe_fd2[PIPE_WRITE_END] > 0) {
143 close(pipe_fd2[PIPE_WRITE_END]);
144 pipe_fd2[PIPE_WRITE_END] = -1;
145 }
146 }
Makefile文件,创建共享库和静态库:
1 CROSSCOMPILE = arm-linux-
2
3 CC=${CROSSCOMPILE}gcc
4 LD=${CROSSCOMPILE}ld
5 AR=${CROSSCOMPILE}ar
6
7 CFLAGS= -O2 -c -Wall -fPIC
8
9 OBJCAT= *.o
10
11 all: libmodem.so.1 libmodem.a
12
13 libmodem.so.1:
14 $(CC) ${CFLAGS} serial.c atchannel.c ppp.c pipe.c interface.c heartbeat.c modem.c
15 $(CC) --shared -o $@ *.o
16
17 libmodem.a:
18 $(CC) ${CFLAGS} serial.c atchannel.c ppp.c pipe.c interface.c heartbeat.c modem.c
19 $(AR) -rcs $@ $(OBJCAT)
20
21 clean :
22 rm *.o *.d *.so.1 *.a -rf
依赖serial.c atchannel.c ppp.c pipe.c interface.c heartbeat.c modem.c七个文件生成libmodem.so.1动态库和libmodem.a静态库。
创建动态库(.so)需要使用两个参数:--shared:表示输出结果是共享库类型的,-fPIC:表示使用地址无关代码技术来生产输出文件
Linux 包含了一个叫 gdb 的 GNU 调试程序. gdb 是一个用来调试 C 和 C++ 程序的强力调试器. 它使你能在程序运行时观察程序的内部结构和内存的使用情况. 以下是 gdb 所提供的一些功能:
- 它使你能监视你程序中变量的值.
- 它使你能设置断点以使程序在指定的代码行上停止执行.
- 它使你能一行行的执行你的代码.
以下演示一个example.c程序的调试过程:example.c文件内容如下,
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<string.h>
4
5 void my_print1(char *string);
6 void my_print2(char *string);
7
8 main()
9 {
10 char my_string[] = "hello there";
11 my_print1(my_string);
12 my_print2(my_string);
13 }
14
15 void my_print1(char *string)
16 {
17 printf ("The string is %s\n", string);
18 }
19
20 void my_print2(char *string)
21 {
22 char *string2;
23 int size, i;
24
25 size = strlen (string);
26 string2 = (char *) malloc (size + 1);
27
28 for (i = 0; i < size; i++)
29 string2[size - i] = string[i];
30
31 string2[size+1] = '\0';
32
33 printf ("The string printed backward is %s\n", string2);
34 }
1.编译时,打开-g选项编译程序,编译后的可执行程序包括调试信息(备注:-E 预处理,生成.i文件;-S 编译,生成.s文件; -c 汇编,生成.o目标文件)
lxw@RD004:~/test/dir_example$ gcc -g example.c -o example
lxw@RD004:~/test/dir_example$ gdb
GNU gdb (GDB) 7.2-ubuntu
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
For bug reporting instructions, please see:
http://www.gnu.org/software/gdb/bugs/.
2.通过file命令加载可执行文件:
(gdb) file example
Reading symbols from /home/lxw/test/dir_example/example...done.
3.通过run命令执行
(gdb)run
Starting program: /home/lxw/test/dir_example/example
The string is hello there
The string printed backward is
Program exited with code 040.
程序异常退出。
4.(gdb) list
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<string.h>
4
5 void my_print1(char *string);
6 void my_print2 (char *string);
7
8 main()
9 {
10 char my_string[] = "hello there";
每次显示十行代码,再次输入list将显示后十行。
5.(gdb) break 29
Breakpoint 1 at 0x4006ad: file example.c, line 29.
设置断点在29行
6.(gdb) next
Breakpoint 1, my_print2 (string=0x7fffffffe2e0 "hello there") at example.c:29
29 string2[size - i] = string[i];
单步运行,类似命令step. (简化命令 n)
7.(gdb) continue
Continuing.
Breakpoint 1, my_print2 (string=0x7fffffffe2e0 "hello there") at example.c:29
29 string2[size - i] = string[i];
继续往下执行 (简化命令 c)
8.(gdb) print i
$3 = 3
打印变量i的值(简化命令 p)
9.(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x00000000004006ad in my_print2 at example.c:29
breakpoint already hit 4 times
查看所有断点信息
10.(gdb) help commandname
查看对应commandname的使用方法,如help breakpoints
11.(gdb) quit
退出gdb高度程序
以上为简单的GDB调试程序例子,详细见gdb帮助文档,附上一篇介绍不错的文章地址,作为收藏[http://dsec.pku.edu.cn/~yuhj/wiki/gdb.html]
编辑器加载中...
1 #include<stdlib.h>
2 #include<stdio.h>
3
4 #define TRUE 1
5 #define FALSE 0
6
7 void bubble_sort(int *num, int numcnt)
8 {
9 int i=0, j=0, exchgnum=0, changeflag=FALSE;
10 for(i=numcnt-1, changeflag=TRUE; i>=1 && changeflag; i--)
11 {
12 changeflag = FALSE;
13 for(j=0; j<i; j++)
14 {
15 if(num[j]>num[j+1])
16 {
17 exchgnum = num[j];
18 num[j]=num[j+1];
19 num[j+1]=exchgnum;
20 changeflag=TRUE;
21 }
22 }
23 }
24 }
25
26 int main()
27 {
28 int num_sort[5], i=0;
29 printf("pls input %d int type data\n", sizeof(num_sort)/sizeof(int));
30 for(i=0; i<(sizeof(num_sort)/sizeof(int)); i++)
31 {
32 scanf("%d", &num_sort[i]);
33 }
34
35 printf("before bubble sort\n");
36 for(i=0; i<(sizeof(num_sort)/sizeof(int)); i++)
37 {
38 printf("numsort[%d]:%d\t", i, num_sort[i]);
39 }
40 printf("\n");
41
42 bubble_sort(num_sort, sizeof(num_sort)/sizeof(int));
43
44 printf("after bubble sort\n");
45 for(i=0; i<(sizeof(num_sort)/sizeof(int)); i++)
46 {
47 printf("numsort[%d]:%d\t", i, num_sort[i]);
48 }
49 printf("\n");
50 }
一. 回顾指针概念:
当我们程序如下申明变量:
short int i;
char a;
short int * pi;
程序会在内存某地址空间上为各变量开辟空间,如下图所示。
内存地址→6 7 8 9 10 11 12 13 14 15
-------------------------------------------------------------------------------------
… | | | | | | | | | |
-------------------------------------------------------------------------------------
|short int i |char a| |short int * pi|
图中所示中可看出:
i 变量在内存地址5的位置,占两个字节。
a变量在内存地址7的位置,占一个字节。
pi变量在内存地址9的位置,占两个字节。(注:pi 是指针,我这里指针的宽度只有两个字节,32位系统是四个字节)
接下来如下赋值:
i=50;
pi=&i;
经过上在两句的赋值,变量的内存映象如下:
内存地址→6 7 8 9 10 11 12 13 14 15
--------------------------------------------------------------------------------------
… | 50 | | | 6 | | | |
--------------------------------------------------------------------------------------
|short int i |char a| |short int * pi|
看到没有:短整型指针变量pi的值为6,它就是I变量的内存起始地址。所以,这时当我们对*pi进行读写操作时,其实就是对i变量的读写操作。如:
*pi=5; //就是等价于I=5;
二. 指针的地址与指向另一指针地址的指针
在上一节中,我们看到,指针变量本身与其它变量一样也是在某个内存地址中的,如pi的内存起始地址是10。同样的,我们也可能让某个指针指向这个地址。
看下面代码:
short int * * ppi; //这是一个指向指针的指针,注意有两个*号
ppi=&pi
第一句:short int * * ppi;——申明了一个指针变量ppi,这个ppi是用来存储(或称指向)一个short int * 类型指针变量的地址。
第二句:&pi那就是取pi的地址,ppi=pi就是把pi的地址赋给了ppi。即将地址值10赋值给ppi。如下图:
内存地址→6 7 8 9 10 11 12 13 14 15
------------------------------------------------------------------------------------
… | 50 | | | 6 | 10 | |
------------------------------------------------------------------------------------
|short int i|char a| |short int * pi|short int ** ppi|
从图中看出,指针变量ppi的内容就是指针变量pi的起始地址。于是……
ppi的值是多少呢?——10。
*ppi的值是多少呢?——6,即pi的值。
**ppi的值是多少呢?——50,即I的值,也是*pi的值。
呵呵!不用我说太多了,我相信你应明白这种指针了吧!
三. 一个应用实例
1. 设计一个函数:void find1(char array[], char search, char * pi)
要求:这个函数参数中的数组array是以0值为结束的字符串,要求在字符串array中查找字符是参数search里的字符。如果找到,函数通过第三个参数(pa)返回值为array字符串中第一个找到的字符的地址。如果没找到,则为pa为0。
设计:依题意,实现代码如下。
void find1(char array[] , char search, char * pa)
{
int i;
for (i=0;*(array+i)!=0;i++)
{
if (*(array+i)==search)
{
pa=array+i
break;
}
else if (*(array+i)==0)
{
pa=0;
break;
}
}
}
你觉得这个函数能实现所要求的功能吗?
调试:
我下面调用这个函数试试。
void main()
{
char str[]={“afsdfsdfdf\0”}; //待查找的字符串
char a=’d’; //设置要查找的字符
char * p=0; //如果查找到后指针p将指向字符串中查找到的第一个字符的地址。
find1(str,a,p); //调用函数以实现所要操作。
if (0==p )
{
printf (“没找到!\n”);//1.如果没找到则输出此句
}
else
{
printf(“找到了,p=%d”,p); //如果找到则输出此句
}
}
分析:
上面代码,你认为会是输出什么呢?
运行试试。
唉!怎么输出的是:没有找到!
而不是:找到了,……。
明明a值为’d’,而str字符串的第四个字符是’d’,应该找得到呀!
再看函数定义处:void find1(char array[] , char search, char * pa)
看调用处:find1(str,a,p);
依我在第五篇的分析方法,函数调用时会对每一个参数进行一个隐含的赋值操作。
整个调用如下:
array=str;
search=a;
pa=p; //请注意:以上三句是调用时隐含的动作。
int i;
for (i=0;*(array+i)!=0;i++)
{
if (*(array+i)==search)
{
pa=array+i
break;
}
else if (*(array+i)==0)
{
pa=0;
break;
}
}
哦!参数pa与参数search的传递并没有什么不同,都是值传递嘛(小语:地址传递其实就是地址值传递嘛)!所以对形参变量pa值(当然值是一个地址值)的修改并不会改变实参变量p值,因此p的值并没有改变(即p的指向并没有被改变)。
(如果还有疑问,再看一看《函数参数的传递》了。)
修正:
void find2(char [] array, char search, char ** ppa)
{
int i;
for (i=0;*(array+i)!=0;i++)
{
if (*(array+i)==search)
{
*ppa=array+i
break;
}
else if (*(array+i)==0)
{
*ppa=0;
break;
}
}
}
主函数的调用处改如下:
find2(str,a,&p); //调用函数以实现所要操作。
再分析:
这样调用函数时的整个操作变成如下:
array=str;
search=a;
ppa=&p; //请注意:以上三句是调用时隐含的动作。
int i;
for (i=0;*(array+i)!=0;i++)
{
if (*(array+i)==search)
{
*ppa=array+i
break;
}
else if (*(array+i)==0)
{
*ppa=0;
break;
}
}
看明白了吗?
ppa指向指针p的地址。
对*ppa的修改就是对p值的修改。
转载自:http://www.51testing.com/?uid-39211-action-viewspace-itemid-80306












