【C语言】 --- 段错误

参考链接

https://blog.csdn.net/xuleilx/article/details/7365424

http://www.blog.chinaunix.net/uid-28414100-id-5759543.html

https://blog.csdn.net/weixin_40005437/article/details/111210315

https://baike.so.com/doc/4335071-4539876.html

一、段错误的概念

存储器区段错误(英语:Segmentation fault,经常被缩写为segfault),又译为存储器段错误,也称访问权限冲突(access violation),是一种程序错误。

它会出现在当程序企图访问CPU无法寻址的存储器区段时。当错误发生时,硬件会通知操作系统产生了存储器访问权限冲突的状况。操作系统通常会产生核心转储(core dump)以方便程序员进行调试。通常该错误是由于调用一个地址,而该地址为空(NULL)所造成的,例如链表中调用一个未分配地址的空链表单元的元素。数组访问越界也可能产生这个错误。(摘抄自wiki)

 

段错误就是指访问的内存超出了系统所给这个程序的内存空间,通常这个值是由gd tr来保存的,他是一个48位的寄存器,其中的32位是保存由它指向的 gdt表,后13位保存 相应于gdt的下标,最后3位包括了程序是否在内存中以及程序的在cpu中的运行级别,指向 的gdt是由以64位为一个单位的表,在这张表中就保存着程序运行的代码段以及数据段的起 始地址以及与此相应的段限和页面交换还有程序运行级别还有内存粒度等等的信息。(摘自360百科)

二、编程中常遇到段错误的地方有那些

1. 指针指向非法内存

定义了指针变量,但是没有为其分配内存,即指针没有指向一块合法的内存。这里列举几个比较隐蔽的例子。

1.1 结构体指针变量的定义和引用错误

#include <stdio.h>

struct student
{
   char *name;
   int score;
}stu,*pstu;

int main()
{
    strcpy(pstu.name,"Jimy");
    stu.score = 99;
    return 0;
}

这里的pstu只是一个结构体指针,并没有分配具体的内存。

直接去引用pstu当然会出错。

关于结构体变量的定义可以查看我之前的一篇文章

1.2 结构体内部指针变量处理错误

还是上面的例子,稍作修改

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

int main(void)
{
	struct student
	{
		char *name = (char *)malloc(sizeof(char) * 10);
		int score;
	}stu, *pstu;

	pstu = (struct student*)malloc(sizeof(struct student));
	strcpy(pstu->name, "Jimy");
	pstu->score = 99;
	free(pstu); 

	system("pause");
	return 0;
}

 这里其实涉及到另外一个问题:

结构体在定义的时候,是不能在结构体内部赋值的。

所以这里的name指针虽然在结构体定义时已经分配空间,但是实际编译时,编译器不会执行后面malloc分配空间的操作。

 

解决方法:在外面手动分配内存

pstu->name = (char *)malloc(sizeof(char *)* 10);

 

2. assert校验指针参数

assert宏的使用说明:

1. assert是一个宏,而不是函数。

2. assert(表达式)。如果表达式中的值为假,则程序停止运行,并提示出错。若为真,则继续运行。

3. assert只在Debug版本起作用,Release版本被编译器优化掉。

当函数使用指针作为参数的时候,在函数入口处使用 assert(p == NULL)进行校验。当然这有一个要求,就是p在定义的时候已经初始化为NULL。

比喻上面1.2中的示例,因为name指针并没有被初始化为NULL,其内部是一个非NULL的乱码,即使校验也没有作用。

2. 指针分配内存太小

为指针分配了内存,但是内存大小不够,导致出现越界错误。

char *p1 = “abcdefg”;
char *p2 = (char *)malloc(sizeof(char)*strlen(p1));
strcpy(p2,p1);

p1 是字符串常量,其长度为7 个字符,但其所占内存大小为8 个byte。初学者往往忘了字符串常量的结束标志“\0”。这样的话将导致p1 字符串中最后一个空字符“\0”没有被拷贝到p2 中。解决的办法是加上这个字符串结束标志符:

char *p2 = (char *)malloc(sizeof(char)*strlen(p1)+1*sizeof(char));

3. 分配内存,但并未初始化

 

4. 内存越界

内存分配成功,且已经初始化,但是操作越过了内存的边界。这种错误经常是由于操作数组或指针时出现“多1”或“少1”。比如:

int a[10] = {0};for (i=0; i<=10; i++)
{
   a[i] = i;
}

5. 内存释放之后

既然使用free 函数之后指针变量p 本身保存的地址并没有改变,那我们就需要重新把p的值变为NULL:  p = NULL; 这个NULL 就是我们前面所说的“栓野狗的链子”。如果你不栓起来迟早会出问题的。比如:在free(p)之后,你用if(NULL != p)这样的校验语句还能起作用吗?例如:

char *p = (char *)malloc(100);
strcpy(p, “hello”);
free(p); /* p 所指的内存被释放,但是p 所指的地址仍然不变*/
…if (NULL != p)
{
  /* 没有起到防错作用*/
  strcpy(p, “world”); /* 出错*/
}

释放完块内存之后,没有把指针置NULL,这个指针就成为了“野指针”,也有书叫“悬垂指针”。这是很危险的,而且也是经常出错的地方。所以一定要记住一条:free 完之后,一定要给指针置NULL。同时留一个问题:对NULL 指针连续free 多次会出错吗?为什么?如果让你来设计free函数,你会怎么处理这个问题?

6. 内存已经释放了,但是继续通过指针使用

这里一般有三种情况:

第一种:就是上面所说的,free(p)之后,继续通过p 指针来访问内存。解决的办法就是给p 置NULL。

第二种:函数返回栈内存。这是初学者最容易犯的错误。比如在函数内部定义了一个数组,却用return 语句返回指向该数组的指针。解决的办法就是弄明白栈上变量的生命周期。

第三种:内存使用太复杂,弄不清到底哪块内存被释放,哪块没有被释放。解决的办法是重新设计程序,改善对象之间的调用关系。

三、如何发现程序中的段错误并处理

1.以一段简单的code来重现core dump错误

#include <stdio.h>

int main(void)
{
	int *p = NULL;
	printf("%d\n", *p);
	return 0;
}

编译运行,看到Segmentation fault了

再执行ls看下,此时并没有看到core dumped文件,问题在哪?

问题在于需要需要系统参数设置

ulimit -c 1024

再去运行刚才的.out文件就可以看到core dump文件了。

2. 通过gdb查看分析core dump文件

运行gdb命令查看core文件

gdb -c core ./a.out

这时候就可以找到错误的原因了。

四、利用backtrace和objdump分析段错误

 

之后在更新吧。。。。今天太晚了

 

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页