【C语言】 --- 段错误

简介: 【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 结构体指针变量的定义和引用错误

1. #include <stdio.h>
2. 
3. struct student
4. {
5.    char *name;
6.    int score;
7. }stu,*pstu;
8. 
9. int main()
10. {
11. strcpy(pstu.name,"Jimy");
12.     stu.score = 99;
13. return 0;
14. }

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

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

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

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

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

1. #define _CRT_SECURE_NO_WARNINGS
2. #include <stdio.h>
3. #include <string.h>
4. 
5. int main(void)
6. {
7.  struct student
8.  {
9.    char *name = (char *)malloc(sizeof(char) * 10);
10.     int score;
11.   }stu, *pstu;
12. 
13.   pstu = (struct student*)malloc(sizeof(struct student));
14.   strcpy(pstu->name, "Jimy");
15.   pstu->score = 99;
16.   free(pstu); 
17. 
18.   system("pause");
19.   return 0;
20. }

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

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

所以这里的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. 指针分配内存太小

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

1. char *p1 = “abcdefg”;
2. char *p2 = (char *)malloc(sizeof(char)*strlen(p1));
3. 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”。比如:

1. int a[10] = {0};for (i=0; i<=10; i++)
2. {
3.    a[i] = i;
4. }

5. 内存释放之后

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

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

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

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

这里一般有三种情况:

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

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

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

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

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

1. #include <stdio.h>
2. 
3. int main(void)
4. {
5.  int *p = NULL;
6.  printf("%d\n", *p);
7.  return 0;
8. }

编译运行,看到Segmentation fault了

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

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

ulimit -c 1024

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

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

运行gdb命令查看core文件

gdb -c core ./a.out

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

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

 

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

 

相关文章
|
C语言
C语言---形参所导致的段错误
C语言---形参所导致的段错误
123 0
|
存储 安全 C语言
关于c语言内存分配,malloc,free,和段错误,内存泄露
1.   C语言的函数malloc和free  (1) 函数malloc和free在头文件中的原型及参数        void * malloc(size_t size) 动态配置内存,大小有size决定,返回值成功时为任意类型指针,失败时为NULL。
2242 0
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
34 3
|
8天前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
25 6
|
28天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
35 10
|
21天前
|
存储 算法 程序员
C语言:库函数
C语言的库函数是预定义的函数,用于执行常见的编程任务,如输入输出、字符串处理、数学运算等。使用库函数可以简化编程工作,提高开发效率。C标准库提供了丰富的函数,满足各种需求。
|
27天前
|
机器学习/深度学习 C语言
【c语言】一篇文章搞懂函数递归
本文详细介绍了函数递归的概念、思想及其限制条件,并通过求阶乘、打印整数每一位和求斐波那契数等实例,展示了递归的应用。递归的核心在于将大问题分解为小问题,但需注意递归可能导致效率低下和栈溢出的问题。文章最后总结了递归的优缺点,提醒读者在实际编程中合理使用递归。
54 7
|
27天前
|
存储 编译器 程序员
【c语言】函数
本文介绍了C语言中函数的基本概念,包括库函数和自定义函数的定义、使用及示例。库函数如`printf`和`scanf`,通过包含相应的头文件即可使用。自定义函数需指定返回类型、函数名、形式参数等。文中还探讨了函数的调用、形参与实参的区别、return语句的用法、函数嵌套调用、链式访问以及static关键字对变量和函数的影响,强调了static如何改变变量的生命周期和作用域,以及函数的可见性。
29 4
|
1月前
|
存储 编译器 C语言
C语言函数的定义与函数的声明的区别
C语言中,函数的定义包含函数的实现,即具体执行的代码块;而函数的声明仅描述函数的名称、返回类型和参数列表,用于告知编译器函数的存在,但不包含实现细节。声明通常放在头文件中,定义则在源文件中。
|
1月前
|
C语言
c语言回顾-函数递归(上)
c语言回顾-函数递归(上)
32 2