您的位置:首页 > 其它

【整理】段错误(segmentation fault)

2011-04-09 13:35 411 查看
1)往受到系统保护的内存地址写数据
有些内存是内核占用的或者是其他程序正在使用,为了保证系统正常工作,所以会受到系统的保护,而不能任意访问.

1
#include<stdio.h>
2
int
3
main()
4
{
5
int
i=0;
6
scanf(
"%d"
,i);
/*shouldhaveused&i*/
7
printf(
"%d\n"
,i);
8
return
0;
9
}
编译和执行一下,咋一看,好像没有问题哦,不就是读取一个数据然后给输出来吗?
falcon@falcon:~/temp$gcc-g-osegerrsegerr.c–加-g选项查看调试信息
falcon@falcon:~/temp$gdb./segerr
GNUgdb6.4-debian
Copyright2005FreeSoftwareFoundation,Inc.
GDBisfreesoftware,coveredbytheGNUGeneralPublicLicense,andyouare
welcometochangeitand/ordistributecopiesofitundercertainconditions.
Type“showcopying”toseetheconditions.
ThereisabsolutelynowarrantyforGDB.Type“showwarranty”fordetails.
ThisGDBwasconfiguredas“i486-linux-gnu”…Usinghostlibthread_dblibrary“/lib/tls/i686/cmov/libthread_db.so.1″.
(gdb)l–用l(list)显示我们的源代码

01
#include<stdio.h>
02
03
int
04
main()
05
{
06
int
i=
0
;
07
08
scanf(”%d”,i);
/*shouldhaveused&i*/
09
printf(”%d\n”,i);
10
return
0
;
(gdb)b8–用b(break)设置断点
Breakpoint1at0×80483b7:filesegerr.c,line8.
(gdb)pi–用p(print)打印变量i的值[看到没,这里i的值是0哦]
$1=0
(gdb)r–用r(run)运行,直到断点处
Startingprogram:/home/falcon/temp/segerr
Breakpoint1,main()atsegerr.c:8
8scanf(”%d”,i);/*shouldhaveused&i*/–[试图往地址0处写进一个值]
(gdb)n–用n(next)执行下一步
10
ProgramreceivedsignalSIGSEGV,Segmentationfault.
0xb7e9a1cain_IO_vfscanf()from/lib/tls/i686/cmov/libc.so.6
(gdb)c–在上面我们接收到了SIGSEGV,然后用c(continue)继续执行
Continuing.
ProgramterminatedwithsignalSIGSEGV,Segmentationfault.
Theprogramnolongerexists.
(gdb)quit–退出gdb
果然
我们“不小心”把&i写成了i
而我们刚开始初始化了i为0,这样我们不是试图向内存地址0存放一个值吗?
[补充:
可以通过man7signal查看SIGSEGV的信息。
falcon@falcon:~/temp$man7signal|grepSEGV
Reformattingsignal(7),pleasewait…
SIGSEGV11CoreInvalidmemoryreference
例子2:

01
#include<stdio.h>
02
int
03
main()
04
{
05
char
*p;
06
p=NULL;
07
*p=‘x’;
08
printf
(”%c”,*p);
09
return
0;
10
}
很容易发现,这个例子也是试图往内存地址0处写东西。
这里我们通过gdb来查看段错误所在的行
falcon@falcon:~/temp$gcc-g-osegerrsegerr.c
falcon@falcon:~/temp$gdb./segerr
GNUgdb6.4-debian
Copyright2005FreeSoftwareFoundation,Inc.
GDBisfreesoftware,coveredbytheGNUGeneralPublicLicense,andyouare
welcometochangeitand/ordistributecopiesofitundercertainconditions.
Type“showcopying”toseetheconditions.
ThereisabsolutelynowarrantyforGDB.Type“showwarranty”fordetails.
ThisGDBwasconfiguredas“i486-linux-gnu”…Usinghostlibthread_dblibrary“/lib/tls/i686/cmov/libthread_db.so.1″.
(gdb)r–直接运行,我们看到抛出段错误以后,自动显示出了出现段错误的行,这就是一个找出段错误的方法
Startingprogram:/home/falcon/temp/segerr
ProgramreceivedsignalSIGSEGV,Segmentationfault.
0×08048516inmain()atsegerr.c:10
10*p=‘x’;
(gdb)

2)内存越界(数组越界,变量类型不一致等)

1
#include<stdio.h>
2
int
3
main()
4
{
5
char
test[1];
6
printf(”%c”,test[1000000000]);
7
return
0;
8
}
这里是比较极端的例子,但是有时候可能是会出现的,是个明显的数组越界的问题
或者是这个地址是根本就不存在的
例子4:

1
#include<stdio.h>
2
int
3
main()
4
{
5
int
b=10;
6
printf
(”%s\n”,b);
7
return
0;
8
}
我们试图把一个整数按照字符串的方式输出出去,这是什么问题呢?
由于还不熟悉调试动态链接库,所以
我只是找到了printf的源代码的这里
声明部分:

01
int
pos=0,cnt_printed_chars=0,i;
02
unsigned
char
*chptr;
03
va_list
ap;
04
/*%s格式控制部分:*/
05
case
's'
:
06
chptr=
va_arg
(ap,unsigned
char
*);
07
i=0;
08
while
(chptr[i])
09
{...
10
cnt_printed_chars++;
11
putchar
(chptr[i++]);
12
}
由于我没有仔细分析代码,大致的原因也可能是地址越界的原因?不过我可不确定哦。
如果大家知道怎么调试printf函数,麻烦帮忙找出越界的真正原因吧,这个段错误也可能是
处在va_start和va_arg等函数里头?或者直接看看这个这里的printf源代码的分析,看看是否
可以找出出错的地方:http://www.wangchao.net.cn/bbsdetail_47325.html类似的,还有诸如:sprintf等的格式控制问题
比如,试图把char型或者是int的按照%s输出或存放起来,如:

01
#include<stdio.h>
02
#include<string.h>
03
char
c=’c';
04
int
i=10;
05
char
buf[100];
06
printf
(”%s”,c);
//试图把char型按照字符串格式输出
07
printf
(”%s”,i);
//试图把int型按照字符串输出
08
memset
(buf,0,100);
09
sprintf
(buf,“%s”,c);
//试图把char型按照字符串格式转换
10
memset
(buf,0,100);
11
sprintf
(buf,“%s”,i);
//试图把int型按照字符串转换
3)其他
其实大概的原因都是一样的,就是段错误的定义。
但是更多的容易出错的地方就要自己不断积累,不段发现,或者吸纳前人已经积累的经验,并且注意避免再次发生。
例如:
<1>定义了指针后记得初始化,在使用的时候记得判断是否为NULL
<2>在使用数组的时候是否被初始化,数组下标是否越界,数组元素是否存在等
<3>在变量处理的时候变量的格式控制是否合理等
一个比较不错的例子:
我在进行一个多线程编程的例子里头,定义了一个线程数组
#defineTHREAD_MAX_NUM
pthread_tthread[THREAD_MAX_NUM];
用pthread_create创建了各个线程,然后用pthread_join来等待线程的结束
刚开始
我就直接等待,在创建线程都成功的时候,pthread_join能够顺利等待各个线程结束
但是一旦创建线程失败,那用pthread_join来等待那个本不存在的线程时自然会存在访问不存在的内存的情况,从而导致段错误的发生
后来
通过不断调试和思考,并且得到网络上资料的帮助,找到了上面的出错原因和解决办法
解决办法是:
在创建线程之前,先初始化我们的线程数组
在等待线程的结束的时候,判断线程是否为我们的初始值
如果是的话,说明我们的线程并没有创建成功,所以就不能等拉。
上面给出了很常见的几种出现段错误的地方,这样在遇到它们的时候就容易避免拉。
但是人有时候肯定也会有疏忽的,甚至可能还是会经常出现上面的问题或者其他常见的问题
所以对于一些大型一点的程序,如何跟踪并找到程序中的段错误位置就是需要掌握的一门技巧拉。

4。如何发现程序中的段错误?
有个网友对这个做了比较全面的总结,除了感谢他外,我把地址弄了过来。
文章名字叫《段错误bug的调试》
地址是:http://www.cublog.cn/u/5251/showart.php?id=173718
应该说是很全面的。

而我常用的调试方法有:
1)在程序内部的关键部位输出(printf)信息,那样可以跟踪段错误在代码中可能的位置
为了方便使用这种调试方法,可以用条件编译指令#ifdefDEBUG和#endif把printf函数给包含起来,编译的时候加上-DDEBUG参数就可以查看调试信息。反之,不加上该参数进行调试就可以。
2)用gdb来调试,在运行到段错误的地方,会自动停下来并显示出错的行和行号
这个应该是很常用的,如果需要用gdb调试,记得在编译的时候加上-g参数,用来显示调试信息

对于这个,网友在《段错误bug的调试》文章里创造性的使用这样的方法,使得我们在执行程序的时候就可以动态扑获段错误可能出现的位置:
通过扑获SIGSEGV信号来触发系统调用gdb来输出调试信息。
如果加上上面提到的条件编译,那我们就可以非常方便的进行段错误的调试拉。

[align=center][/align]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: