C 段错误 Segmentation Fault
本篇文章将讨论 C 中的分段错误,并展示一些代码示例来解释此错误的原因。 首先,我们将讨论程序段和动态内存。
稍后,我们将探讨分段错误的不同原因和可能的解决方案。
C 中的程序段
计算机内存分为主内存和辅助内存。 它必须加载主内存 (RAM) 中的每个程序才能执行程序。
程序被进一步划分成不同的片段。
程序有五个主要部分。 这些细分是:
- 文本段 - 文本段包含程序的代码。
- 初始化数据段——初始化数据段包含程序的全局初始化变量。
-
未初始化的数据段——未初始化的数据段包含程序的全局未初始化变量。 此部分也称为 bss(更好地节省空间)。
有时,我们声明一个大的未初始化数组,如int x[1000]
,需要 4000 个字节; 但是,在初始化数组之前不需要此空间。
因此,这个空间没有保留; 只保存一个指针。 初始化数组时分配内存。 - 堆 - 堆部分包含在运行时请求内存的数据,因为程序员在编码时不知道确切的大小。
- 栈 - 堆栈部分包含所有局部变量。 每当一个函数被调用时,它的局部变量就会被压入栈中(因此栈会增长),当函数返回时,局部变量会被弹出(栈会缩小)。
要可视化并更好地理解程序段,请参见以下代码:
int x = 5;
int y[100];
int main(){
int number = 5;
int *x = new int[5];
return 0;
}
本程序中,x为全局初始化数据,y为全局未初始化数据。 接下来,数字是一个局部变量; 去一个堆栈区域。
x 是一个指针,也是一个局部变量,指向堆栈区域。 new int[5] 为堆区分配空间。
在 Unix 系列操作系统中,你可以很容易地看到这个程序的段。
C 中的动态内存
在许多程序中,程序员并不知道确切的内存需求。 在这种情况下,程序员要么从用户或文件中获取输入来获取数据大小,然后根据输入在运行时声明内存。
看一个例子:
int main(){
int size;
cout << "Enter Size: ";
cin >> size
int *x = (int*) malloc(size] * sizeof(int) );
...
return 0;
}
在这个程序中,用户输入大小,程序在运行时根据大小分配内存。
在多道程序环境中,操作系统需要提供内存保护。 即限制程序在未经其自愿的情况下共享数据。
因此,每个操作系统都保留了一些机制来阻止程序访问非法内存。
我们只能访问为我们的程序保留的内存。 如果我们尝试访问程序地址空间之外的地址,或者程序分配的内存不足以满足动态分配请求,则可能会发生分段错误。
让我们详细讨论一下。
C 中的分段错误 - Segmentation Fault
当您尝试访问程序无法访问的内存位置或没有访问内存的权限时,就会发生分段错误。 让我们在下面讨论一些案例。
尝试引用未初始化的指针
此错误可能会造成混淆,因为某些编译器会针对某些情况发出警告并帮助您避免此错误,而其他编译器则不会。 下面是一个令人困惑的有趣示例。
int main(){
int* pointer;
printf("%d\n", *pointer);
return 0;
}
在上面的代码中,我们试图取消引用一个未分配的指针,这意味着试图访问我们没有访问权限的内存。
使用 (cygwin) GCC 9.3.0 版编译此程序会出现分段错误(核心已转储)。
如果我们用 g++ 9.3.0 编译它,那么它会打印零。
现在,如果我们稍微改变一下这个程序并添加一个函数:
void access(){
int* pointer;
printf("%d\n", *pointer);
}
int main(){
access();
return 0;
}
现在两者都将打印垃圾值编译为输出,这令人困惑,因为我们仍在尝试访问未分配的内存。
如果我们在任何在线编译器上尝试这个,它都有类似的行为。 段错误异常; 有时,进行小的更改可以添加或删除此错误。
为避免此类错误,请记住初始化指针,并在取消引用之前检查指针是否不为空。
尝试分配大内存
此错误可能有两种方式。 一个是在堆栈上声明一个大数组,另一个是在堆上声明一个大内存。
我们将一一看到。
#include <stdio.h>
int main(){
int largeArray[10000000]; // allocating memory in stack
printf("Ok\n");
return 0;
}
如果减少零的数量,输出将是 Ok; 然而,如果你继续增加零,在某些时候,程序会崩溃并给出:
Segmentation fault
原因是堆栈区域是有限的。 这意味着这个大数组所需的内存不可用。
最终,您的程序正试图超出该段。
如果我们需要更多内存(大于堆栈上的可用内存),我们可以使用堆。 但是,堆也有限制; 因此,如果我们不断增加内存大小,就会出现错误。
请参见下面的示例。
#include <stdio.h>
#include <stdlib.h>
int main(){
int *veryLargeArr;
long int size = 100000000000;
veryLargeArr = (int*) malloc (sizeof(int)*size);
if(veryLargeArr == NULL)
printf("Space is not enough.\n");
else
printf("memory allocation is successful\n");
return 0;
}
输出结果:
memory allocation is successful
但是,如果我们继续增加大小,就会超出限制。 例如,我们遇到了以下大小的问题:
long int size = 1000000000000000;//100000000000
你可以在上面的语句中计算更多的零。 在这种情况下,程序可能会崩溃; 但是,为了避免分段错误,我们检查了指针是否为 NULL。
NULL 表示没有分配内存,因为请求的空间不可用。
输出结果:
Space is not enough.
如果您在不检查的情况下使用动态分配的内存来尝试此代码,您将遇到分段错误。
无限循环或递归调用
如果你错误地在你的程序中留下一个无限循环,那将导致段错误,特别是如果你在循环内分配动态内存。
给出了具有动态内存分配的无限循环的示例。
#include <stdio.h>
#include <stdlib.h>
int main(){
int *p;
while (true)
p = (int*) malloc(100000);
return 0;
}
在这里,您可以看到使用 hile (true)
的无限循环。 循环内部的内存分配语句最终会产生错误,因为内存分配是反复重复的,而没有调用 free
方法释放内存。
同样,在不添加基本情况的情况下创建递归函数也会导致堆栈溢出。 请参见下面的示例。
void check(){
check();
}
int main(){
check();
}
在上面的代码中,检查函数将不断调用自身并在堆栈上创建它的副本,一旦程序的可用内存被消耗,这将导致段错误。
总结
当程序试图访问超出其范围或不可用的内存时,就会发生分段错误。 在取消引用之前检查指针是否指向任何内存。
如果需要大空间,则使用动态内存,并检查指针是否为 NULL。 确保在 scanf
中的变量之前使用 &
并在 printf
中的 %
之后使用正确的说明符。
不要尝试从数组中分配或访问超出其大小的任何值。 始终在声明时初始化变量和指针。
相关文章
在 C 语言中使用 typedef enum
发布时间:2023/05/07 浏览次数:181 分类:C语言
-
本文介绍了如何在 C 语言中使用 typedef enum。使用 enum 在 C 语言中定义命名整数常量 enum 关键字定义了一种叫做枚举的特殊类型。
C 语言中的 extern 关键字
发布时间:2023/05/07 浏览次数:114 分类:C语言
-
本文介绍了如何在 C 语言中使用 extern 关键字。C 语言中使用 extern 关键字来声明一个在其他文件中定义的变量
C 语言中的 #ifndef
发布时间:2023/05/07 浏览次数:186 分类:C语言
-
本文介绍了如何在 C 语言中使用 ifndef。在 C 语言中使用 ifndef 保护头文件不被多次包含 C 语言中的头文件用于定义同名源文件中实现的函数的接口。