Segment fault 之所以能够流行于世,是与Glibc库中基本所有的函数都默认型参指针为非空有着密切关系的。
来自:http://oss.lzu.edu.cn/blog/article.php?uid_7/tid_700.html#comment
背景
最近一段时间在linux下用C做一些学习和开发,但是由于经验不足,问题多多。而段错误就是让我非常头痛的一个问题。不过,目前写一个一千行左右的代码,也很少出现段错误,或者是即使出现了,也很容易找出来,并且处理掉。
那什么是段错误?段错误为什么是个麻烦事?以及怎么发现程序中的段错误以及如何避免发生段错误呢?
一方面为了给自己的学习做个总结,另一方面由于至今没有找到一个比较全面介绍这个虽然是“FREQUENTLY ASKED QUESTIONS”的问题,所以我来做个抛砖引玉吧。下面就从上面的几个问题出发来探讨一下“Segmentation faults"吧。
目录
1。什么是段错误?
2。为什么段错误这么“麻烦”?
3。编程中通常碰到段错误的地方有哪些?
4。如何发现程序中的段错误并处理掉?
正文
1。什么是段错误?
下面是来自Answers.com的定义:
A segmentation fault (often shortened to segfault) is a particular error condition that can occur during the operation of computer software. In short, a segmentation fault occurs when a program attempts to access a memory location that it is not allowed to access, or attempts to access a memory location in a way that is not allowed (e.g., attempts to write to a read-only location, or to overwrite part of the operating system). Systems based on processors like the Motorola 68000 tend to refer to these events as Address or Bus errors. |
另外,这里有个基本上对照的中文解释,来自http://www.linux999.org/html_sql/3/132559.htm
所谓的段错误 就是指访问的内存超出了系统所给这个程序的内存空间,通常这个值是由gdtr来保存的,他是一个48位的寄存器,其中的32位是保存由它指向的gdt表, 后13位保存相应于gdt的下标,最后3位包括了程序是否在内存中以及程序的在cpu中的运行级别,指向的gdt是由以64位为一个单位的表,在这张表中 就保存着程序运行的代码段以及数据段的起始地址以及与此相应的段限和页面交换还有程序运行级别还有内存粒度等等的信息。一旦一个程序发生了越界访 问,cpu就会产生相应的异常保护,于是segmentation fault就出现了 |
通过上面的解释,段错误应该就是访问了不可访问的内存,这个内存区要么是不存在的,要么是受到系统保护的。
2。为什么段错误这么麻烦?
中国linux论坛有一篇精华帖子《Segment fault 之永远的痛》(http://www.linuxforum.net/forum/gshowflat.php?Cat=&Board=program&Number=193239&page=2&view=collapsed&sb=5&o=all&fpart=1&vc=1)
在主题帖子里头,作者这么写道:
写程序好多年了,Segment fault 是许多C程序员头疼的提示。指针是好东西,但是随着指针的使用却诞生了这个同样威力巨大的恶魔。 |
后面有好多网友都跟帖了,讨论了Segmentation faults为什么这么“痛”,尤其是对于服务器程序来说,是非常头痛的,为了提高效率,要尽量减少一些不必要的段错误的“判断和处理”,但是不检查又可能会存在段错误的隐患。
那么如何处理这个“麻烦”呢?
就像人不可能“完美”一样,由人创造的“计算机语言“同样没有“完美”的解决办法。
我们更好的解决办法也许是:
通过学习前人的经验和开发的工具,不断的尝试和研究,找出更恰当的方法来避免、发现并处理它。对于一些常见的地方,我们可以避免,对于一些“隐藏”的地方,我们要发现它,发现以后就要及时处理,避免留下隐患。
下面我们可以通过具体的实验来举出一些经常出现段错误的地方,然后再举例子来发现和找出这类错误藏身之处,最后处理掉。
3。编程中通常碰到段错误的地方有哪些?
为了进行下面的实验,我们需要准备两个工具,一个是gcc,一个是gdb
我是在ubuntu下做的实验,安装这两个东西是比较简单的
sudo apt-get install gcc-4.0 libc6-dev |
好了,开始进入我们的实验,我们粗略的分一下类
1)往受到系统保护的内存地址写数据
有些内存是内核占用的或者是其他程序正在使用,为了保证系统正常工作,所以会受到系统的保护,而不能任意访问。
例子1:
Code:
#include <stdio.h>
int main(){
int i=0;
scanf("%d",i);
printf("%dn",i);
return 0;
}
编译和执行一下
$ gcc -o segerr segerr.c |
咋一看,好像没有问题哦,不就是读取一个数据然后给输出来吗?
下面我们来调试一下,看看是什么原因?
$ gcc -g -o segerr segerr.c --加-g选项查看调试信息 |
果然
我们“不小心”把&i写成了i
而我们刚开始初始化了i为0,这样我们不是试图向内存地址0存放一个值吗?实际上很多情况下,你即使没有初始化为零,默认也可能是0,所以要特别注意。
补充:
可以通过man 7 signal查看SIGSEGV的信息。
$ man 7 signal | grep SEGV |
例子2:
Code:
#include <stdio.h>
int main(){
char *p;
p = NULL;
*p = 'x';
printf("%c", *p);
return 0;
}
很容易发现,这个例子也是试图往内存地址0处写东西。
这里我们通过gdb来查看段错误所在的行
$ gcc -g -o segerr segerr.c |
2)内存越界(数组越界,变量类型不一致等)
例子3:
Code:
#include <stdio.h>
int main(){
char test[1];
printf("%c", test[1000000000]);
return 0;
}
这里是比较极端的例子,但是有时候可能是会出现的,是个明显的数组越界的问题
或者是这个地址是根本就不存在的
例子4:
Code:
#include <stdio.h>
int main(){
int b = 10;
printf("%sn", b);
return 0;
}
我们试图把一个整数按照字符串的方式输出出去,这是什么问题呢?
由于还不熟悉调试动态链接库,所以
我只是找到了printf的源代码的这里
声明部分: |
仔 细看看,发现了这样一个问题,在打印字符串的时候,实际上是打印某个地址开始的所有字符,但是当你想把整数当字符串打印的时候,这个整数被当成了一个地 址,然后printf从这个地址开始去打印字符,直到某个位置上的值为
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/2799/viewspace-2823827/ ,如需转载,请注明出处,否则将追究法律责任。