什么是分段错误?

867

什么是段错误?在C和C++中有什么不同?段错误与悬空指针有什么关系?


4
出现问题时只是一个内存转储! - resultsway
1
通常被尝试取消引用空指针调用,因此分段错误通常类似于Java的NullPointerException - Raedwald
3
“分段”源自于内存分段。此时,您正在访问不属于您的内存“段”。 - kotchwane
17个回答

951

段错误是一种特定类型的错误,由于访问"不属于你"的内存而引起。它是一个帮助机制,可以防止破坏内存并引入难以调试的内存错误。每当你遇到段错误时,就知道你在处理内存方面出了问题 - 访问已被释放的变量、写入只读内存部分等。在大多数允许操作内存管理的语言中,段错误基本上是相同的,在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
悬空指针指向一个不存在的东西,就像这里:
char *p = NULL;
{
    char c;
    p = &c;
}
// Now p is dangling

指针p失效了,因为它指向的字符变量c在代码块结束时就不存在了。如果你尝试解引用这个失效的指针(比如*p='A'),很可能会导致段错误。


205
最后一个例子非常恶劣,当我编写以下代码时:int main() { char *p = 0; { char c = 'x'; p = &c; } printf( "%c\n",*p); return 0; }使用gcc或其他几个编译器都“似乎”可以工作。编译时没有警告,也不会导致段错误。这是因为‘}’离开范围时,并不会实际删除数据,只是将其标记为空闲状态以便再次使用。该代码可能在生产系统上运行多年,直到您更改代码的另一部分、更改编译器或发生其他问题,程序才会崩溃。 - Chris Huang-Leaver
52
抱歉打扰一下,只是顺便提一句……你的例子中没有一个必定会导致段错误(segfault),实际上这只是未定义行为。;-) - obataku
32
@oldrinb:无法编写必然导致段错误的代码。其中一个原因是,有些系统不提供内存保护机制,因此无法确定某个内存是否真正属于你,因此不知道什么是段错误,只能表现为未定义的行为...(例如经典的AmigaOS)。 - DevSolar
10
@ChrisHuang-Leaver,你需要了解,变量“c”是本地的,也就是说,在“{”之后它被压入栈中,在“}”之后被弹出。悬空指针只是一个指向栈外偏移量的引用。这就是为什么在简单程序中修改它不会触发任何段错误。然而,在更复杂的使用情况下,其他函数调用可能会导致堆栈增长并包含悬空指针所指向的数据。写入该数据(本地变量)将导致未定义行为(段错误等)。 - Ayman Khamouma
5
@ChrisHuang-Leaver,通常情况下当你超出作用域时,编译器需要恢复一些栈空间以释放未使用的栈空间,但并不总是发生这种情况(其中gcc就是其中之一)。此外,分配的栈空间通常会被再次重复使用,因此我听说没有操作系统将未使用的栈页面返回给系统,使得该空间容易受到“SIGSEGV”的影响,所以我不会期望由于操纵栈而出现这样的信号。 - Luis Colorado
显示剩余4条评论

130
值得注意的是,段错误并不是由于直接访问另一个进程的内存造成的(这是我有时听到的),因为这根本不可能。使用虚拟内存,每个进程都有自己的虚拟地址空间,并且没有办法使用任何指针值访问另一个进程。这个规则有例外情况,例如共享库,它们是同一物理地址空间映射到(可能)不同的虚拟地址以及内核内存,即使在每个进程中也是以相同的方式映射(以避免在系统调用时刷新TLB,我想)。还有像shmat这样的东西;)- 这些是我所谓的“间接”访问。然而,人们可以检查它们通常远离进程代码,并且我们通常能够访问它们(这就是为什么它们在那里),但是以不正确的方式访问它们将会产生段错误。
依然,如果以不当的方式访问我们自己(进程)的内存(例如试图写入非可写空间),就可能发生段错误。但最常见的原因是访问没有映射到物理地址空间的虚拟地址空间的一部分。
所有这些都与虚拟内存系统有关。

1
通过共享内存/内存映射文件,他人有可能干扰您的内存。在WIN32中,还存在像“WriteProcessMemory”这样的恶意API! - paulm
3
@paulm:是的,我知道。这就是我所说的“像shmat这样的东西;) - 这些是我认为是‘间接’访问”的意思。 - konrad.kruczynski
1
在虚拟内存操作系统中,通常情况下(所以,请操作系统实现者不要因此而责备我),一个进程无法访问另一个进程的虚拟内存,除非使用某种内存附加系统调用来进行访问。虚拟内存地址通常意味着不同的含义,具体取决于所考虑的进程。 - Luis Colorado

49

分段错误是由于进程请求的页面未在其描述符表中列出,或者请求一个已列出但不合法的页面(例如,在只读页面上进行写入请求)。

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


24
没错,但如果你本来就不知道什么是分段错误,那这真的会对你有所帮助吗? - zoul

38

说实话,正如其他帖子所提到的,维基百科有一篇非常好的文章请在那里查看.这种类型的错误非常常见,通常被称为访问冲突或通用保护错误。

在允许使用指针的任何语言中,它们都没有任何区别。 这些类型的错误通常是由于指针在以下情况下引起的:

  1. 在适当初始化之前使用
  2. 在其指向的内存已被重新分配或删除后使用
  3. 在索引数组中使用,其中索引超出数组边界。 通常仅在传统数组或C字符串上进行指针数学运算时才会出现此类问题,而不是在基于STL / Boost的集合(在C ++中)上。

23
根据维基百科的解释:
当程序试图访问未被允许访问的内存位置,或以不允许的方式访问内存位置(例如尝试写入只读位置或覆盖操作系统的一部分)时,会发生段错误。

14

段错误也可能由硬件故障引起,这种情况下是因为RAM内存问题。这是较少见的原因之一,但如果您在代码中找不到错误,也许可以通过运行内存测试来帮助解决。

在这种情况下,解决方案是更换RAM。

编辑:

这里有一个参考链接:硬件引起的段错误


7
检测有缺陷的内存的一种快速而简单的方法是在循环中反复运行崩溃的程序。如果程序没有任何内部不确定性,即对于相同的输入它总是产生相同的输出,或者至少应该如此,但是对于某些特定输入,它偶尔会崩溃,不一定总是,也不一定从未出现:那么您应该开始担心内存损坏的可能性。 - zwol

13

维基百科的“段错误”页面提供了一个非常好的描述,仅指出了其中的原因和理由。详细信息请查看维基百科。

在计算机中,段错误(通常缩写为 segfault)或访问冲突是由具备内存保护功能的硬件引发的故障,通知操作系统(OS)存在内存访问违规。

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

  • 解除引用 NULL 指针-这是由内存管理硬件进行特殊处理
  • 尝试访问不存在的内存地址(超出进程地址空间)
  • 尝试访问程序没有权限的内存(例如处理上下文中的内核结构)
  • 尝试写入只读内存(例如代码段)

这些又往往是由导致非法访问内存的编程错误引起的:

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

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

  • 缓冲区溢出。

  • 堆栈溢出。

  • 尝试执行无法正确编译的程序。(一些编译器将在存在编译时错误的情况下输出可执行文件。)


9

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

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

6
正确删除数组的方式是使用 delete [] arr;。 - Damian

8
简单来说,段错误是操作系统向程序发送一个信号,表明它已经检测到了非法的内存访问,并且为了避免内存被破坏而提前终止了程序。

4
在回答中有几个关于“段错误”(Segmentation fault)的好解释,但由于在发生段错误时通常会出现内存内容的转储,我想分享一下“核心已转储”(core dumped)与内存之间的关系:
从 1955 年到 1975 年左右,半导体存储器问世之前,计算机存储器的主导技术是使用铜线串起来的小型磁性圆环。这些圆环被称为“铁芯”,因此主存被称为“磁芯存储器”或“磁心存储器”。
以上内容摘自这里

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接