对于这个问题,只是之前听说过,并没有研究过。最近在oj编程时,vs上运行没有问题,提交时出现了段错误。为此重视一下这个问题。
在维基上给出这个名词的定义
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 (for example, attempting to write to a read-onlylocation, or to overwrite part of the operating system).
所谓的段错误 就是指访问的内存超出了系统所给这个程序的内存空间,通常这个值是由gdtr来保存的,他是一个48位的寄存器,其中的32位是保存由它指向的gdt表, 后13位保存相应于gdt的下标,最后3位包括了程序是否在内存中以及程序的在cpu中的运行级别,指向的gdt是由以64位为一个单位的表,在这张表中 就保存着程序运行的代码段以及数据段的起始地址以及与此相应的段限和页面交换还有程序运行级别还有内存粒度等等的信息。一旦一个程序发生了越界访 问,cpu就会产生相应的异常保护,于是segmentation fault就出现了(来源网络)
关于一些理论的知识在此,就不叙述了,最后会给出一个博客链接,介绍。
接下来用小代码实验来说明一些问题
一、
#include<stdio.h>
int main()
{
char* s= "hello";
s[1]=‘h‘;
return 0;
}
这段代码很明显是错误的,但是语法没有问题。在vs下编译通过。
然而这里会引起非法写,这里字符串既不是存放在栈也不是在堆里面,是在全局区中,或者说在代码段中,这段内存是不可写的,
vs在运行时出现下面的错误
Unhandled exception at 0x002f1398 in segdefault.exe: 0xC0000005: Access violation writing location 0x002f573d.
二、
#include<stdio.h>
int main()
{
int* s=(int*)0xd000001;
*s=1;
return 0;
}
这段代码从语法上将是没有问题的,vs编译通过,但是有个明显的问题就是,指针指向了未知内存,或者说这是一个野指针,这段内存可能不是操作系统分配给当前进程的,所以操作系统也会产生异常。vs运行出现下面错误
Unhandled exception at 0x00b21398 in segdefault.exe: 0xC0000005: Access violation writing location 0x0d000001.
三、
#include<stdio.h>
int* f()
{
int a=1;
return &a;
}
int main()
{
int* p;
p=f();
printf("%d\n",*p);
return 0;
}
对于这段代码,vs在编译时有一个警告
warning C4172: returning address of local variable or temporary
即返回局部变量地址,也就是说f函数的栈在函数结束时会释放掉,如果返回此时栈里面的内容,实际是不可取的,
看看vs运行的结果
结果没有什么问题,或者说此时指针指向的是当前栈空间之外的区域,发生了栈溢出,但是有个问题就是,当一个函数结束时,他的栈是如何被回收的,
如果看到汇编知道,只是修改了esp这个指针,所以在下一次没有用这块空间栈时,里面的内容是还存在的。
四、
下面这个代码是网上看到的,用于测试堆
#include<stdio.h>
#include<stdlib.h>
#define K 1024
int main()
{
char* c;
int i=0;
c=(char*)malloc(1);
while(1)
{
c+=K;
*c=‘a‘;
printf("overflow%dK\n",i);
i++;
}
return 0;
}
这段代码在vs上运行是不通过,有下面的错误
Unhandled exception at 0x010c33f3 in segdefault.exe: 0xC0000005: Access violation writing location 0x00575208.
而在linux gcc平台下
编译通过并且可以运行,运行结果如下
也就是在堆中,如果一直溢出,当达到130k时就会产生段错误,这个跟os的内存分配有关,以后研究到linux内存分配时在具体找出原因。
五、
#include<stdio.h>
#include<stdlib.h>
#define K 1024
int main()
{
char* c;
int i=0;
c=(char*)malloc(1);
*c=‘a‘;
printf("%p\n",c);
printf("%c\n",*c);
free(c);
printf("%p\n",c);
printf("%c\n",*c);
return 0;
}
这段代码实际测试的是free,free实现是怎么样的,
在linux中
也就是说,free之后,指向指针的地址并没有发生变化,还是指向那块堆空间,但是里面的内容变化了,
将堆中的内容以整数打印出来为
也就是说free之后将分配的堆空间清零了。
六、
#include<stdio.h>
#include<stdlib.h>
#include <string.h>
void f()
{
char c=‘a‘;
memset(&c,0x55,128);
}
int main()
{
f();
printf("return ?\n");
return 0;
}
这个问题在于在函数f中修改了栈空间的内容,因为在进入f之前main会压入f返回后要执行的指令的地址,这样将地址内容修改了,使得返回的是一个野指针。
vs出现下面的错误。
Unhandled exception at 0x55555555 in segdefault.exe: 0xC0000005: Access violation.
所有上面的情况其实认为都是尽量避免的,
在网上看到有人总结了如何小心避免
1、
其 它 程 序 员 不 同 的 是 , 彻 底 的 懂 得 你 的 程 序 。
指 做 到 精 确 控 制 。 尤 其 在 内 存 的 分 配 和 释 放 方 面 。 在 操 作 每 一 个 指 针 前 , 你 都 应 该 清 楚 它 所
向 内 存 的 出 处 ( 栈 、 堆 、 全 局 区 ) , 并 清 楚 此 内 存 的 生 存 周 期 。 只 有 明 白 的 使 用 内 存 , 才 能
的 产 生 。 S I G S E G V 最 大 限 度 的 避免。
2、
大量使用assert,用于检查
3、多关注编译时产生的warning
资料链接
http://blog.csdn.net/jk110333/article/details/19685127(段错误调试)
版权声明:本文为博主原创文章,未经博主允许不得转载。