什么是分割错误?

什么是分割错误?它在C和C++中是否不同?分割错误和悬空指针是如何相关的?

922764 次浏览

分段错误是由请求进程未在其描述符表中列出的页面或对其确实列出的页面的无效请求(例如只读页面上的写入请求)引起的。

悬空指针是指可能指向也可能不指向有效页的指针,但确实指向“意外”内存段。

根据维基百科

分段错误发生在程序尝试访问内存不允许的位置访问或尝试访问内存以不允许的方式定位(例如,尝试写入只读位置,或覆盖操作系统的一部分)。

分割错误是由于访问“不属于你”的内存而导致的一种特定类型的错误。这是一种帮助机制,可以防止你损坏内存并引入难以调试的内存错误。每当你遇到分割错误时,你就知道你在内存中做错了什么——访问已经释放的变量,写入内存的只读部分,等等。分割错误在大多数语言中本质上是相同的,让你搞乱内存管理,C和C++中的分割错误之间没有主要区别。

有很多方法可以获得段错误,至少在C(++)等低级语言中是这样。获取段错误的常见方法是取消引用空指针:

int *p = NULL;*p = 1;

当您尝试写入标记为只读的内存部分时,会发生另一个段错误:

char *str = "Foo"; // Compiler marks the constant string as read-only*str = 'b'; // Which means this is illegal and results in a segfault

Dangling指针指向一个不再存在的东西,就像这里:

char *p = NULL;{char c;p = &c;}// Now p is dangling

指针p悬空是因为它指向在块结束后不再存在的字符变量c。当你尝试取消引用悬空指针(如*p='A')时,你可能会得到一个段错误。

老实说,正如其他海报所提到的,维基百科有一篇关于这个所以看看那里。的非常好的文章这种类型的错误非常常见,通常被称为其他东西,如访问违规或一般保护故障。

它们在C、C++或任何其他允许指针的语言中没有什么不同。这些类型的错误通常是由以下指针引起的

  1. 在正确初始化之前使用
  2. 在它们指向的内存被重新分配或删除后使用。
  3. 用于索引超出数组边界的索引数组中。这通常仅当您对传统数组或c字符串进行指针数学运算时,而不是基于STL/Boost的集合(在C++中)。

值得注意的是,分段错误不是由直接访问另一个进程内存引起的(这是我有时听到的),因为这是根本不可能的。使用虚拟内存,每个进程都有自己的虚拟地址空间,并且无法使用任何指针值访问另一个进程。例外情况是共享库,它们是相同的物理地址空间映射到(可能)不同的虚拟地址和内核内存,甚至在每个进程中以相同的方式映射(我认为是为了避免系统调用上的TLB刷新)。以及像shmat;)这样的东西-这些是我认为的“间接”访问。但是,可以检查它们通常位于离进程代码很远的地方,并且我们通常能够访问它们(这就是它们存在的原因,但是以不正确的方式访问它们会产生分段错误)。

尽管如此,在以不正确的方式访问我们自己的(进程)内存的情况下(例如试图写入不可写空间)可能会发生分段错误。但最常见的原因是对虚拟地址空间中未映射到物理地址空间的访问。

所有这些都与虚拟内存系统有关。

分段故障发生在进程(正在运行的程序实例)试图访问其他进程正在使用的只读内存地址或内存范围或访问不存在(无效)的内存地址时。悬空引用(指针)问题意味着试图访问其内容已从内存中删除的对象或变量,例如:

int *arr = new int[20];delete arr;cout<<arr[1];  //dangling problem occurs here

分段故障也是由硬件故障引起的,在这种情况下是RAM内存。这是不太常见的原因,但如果您在代码中没有发现错误,也许memtest可以帮助您。

在这种情况下,改变RAM的解决方案。

编辑:

这里有一个参考:硬件分割故障

维基百科的Segmentation_fault页面有一个非常好的描述,只是指出了原因和原因。请查看wiki以获取详细描述。

在计算中,分段故障(通常简称为分段故障)或访问冲突是由具有内存保护的硬件引发的故障,通知操作系统(OS)内存访问冲突。

以下是分段错误的一些典型原因:

  • 取消引用NULL指针-这是内存管理硬件的特殊情况
  • 尝试访问不存在的内存地址(在进程的地址空间之外)
  • 试图访问程序无权访问的内存(例如进程上下文中的内核结构)
  • 尝试写入只读内存(例如代码段)

这些反过来通常是由导致无效内存访问的编程错误引起的:

  • 取消引用或分配给未初始化的指针(野生指针,指向随机内存地址)

  • 取消引用或分配给释放的指针(悬空指针,指向已释放/释放/删除的内存)

  • 缓冲区溢出。

  • 堆栈溢出。

  • 试图执行未正确编译的程序。(尽管存在编译时错误,但某些编译器仍会输出可执行文件。)

当程序尝试访问不存在的内存位置或尝试以不允许的方式访问内存位置时,就会发生分段错误或访问冲突。

 /* "Array out of bounds" errorvalid indices for array fooare 0, 1, ... 999 */int foo[1000];for (int i = 0; i <= 1000 ; i++)foo[i] = i;

这里i[1000]不存在,所以发生了分段错误。

分割错误原因:

it arise primarily due to errors in use of pointers for virtual memory addressing, particularly illegal access.
De-referencing NULL pointers – this is special-cased by memory management hardware.
Attempting to access a nonexistent memory address (outside process’s address space).
Attempting to access memory the program does not have rights to (such as kernel structures in process context).
Attempting to write read-only memory (such as code segment).

简单来说:分割故障就是操作系统向程序发送信号说它检测到非法内存访问并提前终止程序以防止内存被破坏。

答案中有几个关于“分割错误”的很好的解释,但是由于分割错误通常会有内存内容的转储,我想分享分段故障(核心转储)中“核心转储”部分与内存之间的关系来自哪里:

从大约1955年到1975年——在半导体存储器之前——计算机存储器的主导技术使用串在铜线上的微小磁性甜甜圈。甜甜圈被称为“铁氧体核心”,主存储器因此被称为“核心存储器”或“核心”。

取自这里

“分割错误”意味着您尝试访问您无权访问的内存。

第一个问题是main的参数。main函数应该是int main(int argc, char *argv[]),在访问ag v[1]之前,您应该检查ag c至少为2。

此外,由于您将浮点数传递给printf(顺便说一句,当传递给printf时,它会转换为双精度),您应该使用%f格式说明符。%s格式说明符用于字符串(以'\0'结尾的字符数组)。

有足够的分割错误定义,我想引用一些我在编程时遇到的例子,这可能看起来像愚蠢的错误,但会浪费很多时间。

  1. 你可以在下面的情况下得到一个分段错误,而参数类型在printf中不匹配:
#include <stdio.h>int main(){int a = 5;printf("%s",a);return 0;}

输出:Segmentation Fault (SIGSEGV)

  1. 当您忘记将内存分配给指针时,请尝试使用它。
#include <stdio.h>typedef struct{int a;} myStruct;int main(){myStruct *s;/* few lines of code */s->a = 5;return 0;}

输出:Segmentation Fault (SIGSEGV)

Segmentation fault的简单含义是您试图访问一些不属于您的内存。当我们尝试在只读内存位置读取和/或写入任务或尝试释放内存时,就会发生Segmentation fault。换句话说,我们可以将其解释为某种内存损坏。

下面我提到程序员经常犯的导致Segmentation fault的错误。

  • 错误地使用scanf()(忘记放&)。
int num;scanf("%d", num);// must use &num instead of num
  • 以错误的方式使用指针。
int *num;printf("%d",*num); //*num should be correct as num only//Unless You can use *num but you have to point this pointer to valid memory address before accessing it.
  • 修改字符串文字(指针尝试写入或修改只读内存。)
char *str;
//Stored in read only part of data segmentstr = "GfG";
//Problem:  trying to modify read only memory*(str+1) = 'n';
  • 尝试通过已经释放的地址到达。
// allocating memory to numint* num = malloc(8);*num = 100;
// de-allocated the space allocated to numfree(num);
// num is already freed there for it cause segmentation fault*num = 110;
  • Stack Overflow-在堆栈上运行内存溢出
  • 超出边界访问数组
  • 使用printf()scanf()时使用错误的格式说明符

考虑以下代码片段,

SNIPPET 1

int *number = NULL;*number = 1;

SNIPPET 2

int *number = malloc(sizeof(int));*number = 1;

如果你问这个问题,我假设你知道函数的含义:malloc()sizeof()

既然已经解决了,SNIPPET 1将抛出分割错误错误。而SNIPPET 2不会。

这就是原因。

第一段的第一行是创建一个变量(*num)来存储其他变量的地址,但在这种情况下,它被初始化为NULL。另一方面,代码段2的第二行是创建相同的变量(*数字)来存储其他地址,在这种情况下,它被赋予一个内存地址(因为malloc()是C/C++中的一个函数,它返回计算机的内存地址)

重点是你不能把水放在一个碗里,还没有被购买或一个碗,已经买了,但没有被授权使用。当您尝试这样做时,计算机会收到警报并抛出SegFault错误。

您应该只在接近低级的语言(如C/C++)中遇到此错误。其他高级语言中有一个抽象,可确保您不会犯此错误。

同样重要的是要明白,分割错误不是特定于语言的。

在计算中,分段错误或访问冲突是由具有内存保护的硬件引发的故障或故障条件,通知操作系统该软件已尝试访问内存的限制区域。-维基百科

您可能正在使用错误的数据类型访问计算机内存。您的案例可能类似于下面的代码:

#include <stdio.h>
int main(int argc, char *argv[]) {    
char A = 'asd';puts(A);    
return 0;    
}

asd->是字符链而不是单个字符char数据类型。因此,将其存储为char会导致分段故障在错误的位置存储一些数据。

将此string或字符链存储为单个char是试图将方形钉放入圆孔中。

由于信号终止:分割故障(11)

Segm。故障与试图在水下呼吸是一样的,你的肺不是为此而生的。为整数保留内存,然后尝试将其作为另一种数据类型进行操作根本不起作用。

分段故障(有时称为赛格弗)发生当您的程序尝试访问它不允许访问的内存时。换句话说,当您的程序尝试访问超出操作系统为您的程序设置的边界的内存时。这是导致程序崩溃的常见情况;它通常与一个名为core的文件有关。

程序内存分为不同的段:

  • 用于程序指令的文本段
  • 编译时定义的变量和数组的数据段
  • 子例程和函数中定义的临时(或自动)变量的堆栈段
  • 一个堆段,用于在运行时由函数分配的变量,例如malloc(在C中)和分配(在Fortran中)。

当对变量的引用超出该变量存在的段时,或者当尝试向只读段中的某个位置写入时,会发生段错误。实际上,段错误几乎通常是由尝试读取或写入不存在的数组成员、在使用指针之前未能正确定义指针或(在C应用程序中)无意中使用变量的值作为地址引起的(参见下面的扫描示例)。

*例如,调用memset()会导致程序出现段错误:

memset((char *)0x0, 1, 100);

*<强>下面的三个示例显示了最常见的与数组相关的分段故障类型:

案件a

/* "Array out of bounds" error valid indices for array foo are 0, 1, ... 999 */int foo[1000]; for (int i = 0; i <= 1000 ; i++) foo[i] = i;

案件b

/* Illegal memory access if value of n is not in the range 0, 1, ... 999 */int n; int foo[1000]; for (int i = 0; i < n ; i++) foo[i] = i;

案例c

/* Illegal memory access because no memory is allocated for foo2 */float *foo, *foo2; foo = (float*)malloc(1000); foo2[0] = 1.0;
  • 在情况A中,数组foo是为索引=0,1,2,…999定义的。然而,在进行循环的最后一次迭代中,程序试图访问Foo[1000]。如果该内存位置位于foo所在的内存段之外,这将导致段故障。即使它不会导致段故障,它仍然是一个bug。
  • 在情况B中,整数n可以是任何随机值。与情况A一样,如果它不在范围0,1,…999中,则可能导致段错误。无论是否,它肯定是一个bug。
  • 在情况C中,忽略了变量fo2的内存分配,因此fo2将指向内存中的随机位置。访问fo2[0]可能会导致段错误。

*另一个导致段错误的典型编程问题是未能正确使用指针。例如,C函数scanf()需要变量的地址作为其第二个参数;因此,以下内容肯定会导致程序以段错误失败:

int foo = 0; scanf("%d", foo);/* Note missing & sign ; correct usage would have been &foo */

尽管变量foo可以在内存位置1000创建,但前面的函数调用将尝试根据foo的定义将整数值读取到内存位置0。

当软件试图以未经授权的方式对内存区域进行操作时(例如,尝试写入只读位置将导致段故障),就会发生段故障。当您的应用程序耗尽堆栈空间时,就会发生段故障。这可能是由于您的shell将堆栈大小限制设置得太低,而不是软件中的故障。

悬挂指针指向不再存在的东西。悬空指针就是一个例子。

char *ptr = NULL;{char c;ptr = &c; //After the block is over, ptr will be a dangling pointer.}

当块结束时,变量c的作用域到期。因为它现在指向不存在的东西,'ptr'将成为悬空指针。

但是当您尝试访问不属于您的内存或尝试写入只读区域时,您会得到分段故障

char *str ="Testing Seg fault.";*str= "I hate Seg fault :( ";

'str'将被编译器设置为常量。当您尝试更新值时,您正在更改只读部分,从而导致分段错误。因此,分段错误和悬空指针之间有明显的区别。