2010-02-27 141 views
402

什么是分段错误? C和C++有什么不同?分段错误和悬挂指针如何相关?什么是分段错误?

+55

段错误使[编译器感觉不好](http://xkcd.com/371/)。 – 2011-09-12 14:32:19

+14

如果是这样的话,为什么在我的情况下编译器什么也没有抱怨,一切都很顺利,但是在运行时系统会抛出一个分段错误(核心转储)? T_T – 2015-01-05 19:46:23

+2

只要内存转储出现问题! – resultsway 2015-04-25 01:18:58

回答

500

分段错误是由于访问“不属于你”的内存导致的一种特定类型的错误。它是帮助您避免损坏内存并引入难以调试的内存错误的辅助机制。每当你遇到段错误时,你都知道你在做内存错误 - 访问已经释放的变量,写入内存的只读部分等等。在大多数语言中,分段错误基本上是相同的,内存管理,C和C++中的段错误之间没有主要区别。

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

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

党岭:

int *p = NULL; 
*p = 1; 

另一个段错误当您尝试写入到被标记为只读存储器的一部分发生指针指向一个东西不存在了,喜欢这里:

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

指针p悬,因为它指向字符变量在块结束后不再存在的。而当你试图解引用悬挂指针(如*p='A')时,你可能会遇到段错误。

+115

最后一个例子特别讨厌,当我构建时: int main() { char * p = 0; { char c ='x'; p =&c; } printf(“%c \ n”,* p); return 0; } 无论是gcc或其他几个编译器,它似乎工作。编译时没有警告。没有段错误。这是因为'}'超出范围,并不实际删除数据,只是将其标记为可以再次使用。该代码可以在生产系统上运行很多年,您可以更改代码的另一部分,更改编译器或其他内容,并且BOOOOOM! – 2010-04-13 09:06:16

+21

对不起,但只是一个侧面说明...你的例子没有一定会导致段错误,实际上它只是未定义的行为;-) – oldrinb 2012-09-15 03:01:21

+10

@oldrinb:这是不可能编写的代码,*必然*导致段错误。不仅仅是因为那里有没有内存保护的系统,因此不能分辨出一段内存实际上“属于你”,因此**不知道**段错误,只有未定义的行为......(经典的AmigaOS ,例如) – DevSolar 2014-05-29 18:03:48

25

分段错误是由于进程未在其描述符表中列出的页面请求或其列出的页面的无效请求导致的(例如,在只读页面上发出写请求)。

一个悬挂指针是一个指针,它可能指向一个有效页面,也可能不指向一个有效页面,但会指向一个“意外”的内存段。

+6

这是真的,但如果你已经不知道分段错误是什么,它会真的帮助你吗? – zoul 2010-02-27 09:37:18

14

根据Wikipedia:

当 程序试图访问它不允许 存取存储器 位置时,会发生段错误,或企图的方式访问存储器 位置即不允许 (例如,试图写入到 只读位置,或覆盖 部分操作系统)。

21

说实话,正如其他海报所提到的,维基百科在这方面有一篇很好的文章so have a look there.这种类型的错误非常常见,并且经常被称为其他事情,例如访问冲突或常规保护错误。

它们在C,C++或任何其他允许指针的语言中没有区别。这些类型的错误通常是由那些

  1. 被正确初始化
  2. 他们指出,已经realloced或删除内存后之前,使用指针引起的。
  3. 用于索引位于数组边界之外的索引数组中。这通常只适用于在传统数组或C字符串上进行指针计算,而不是基于STL/Boost的集合(使用C++)。
83

值得注意的是,段错误不是直接导致的访问另一个进程内存(这是我有时听到的),因为它根本不可能。使用虚拟内存,每个进程都有自己的虚拟地址空间,并且无法使用任何指针值访问另一个进程。例外是共享库,这些共享库映射到(可能)不同的虚拟地址和内核内存,它们在每个进程中都以相同的方式映射(为了避免系统调用时TLB刷新,我认为)。而像shmat这样的东西) - 这些就是我所谓的“间接”访问。但是,可以检查它们通常位于远离流程代码的位置,我们通常可以访问它们(这就是为什么它们在那里,但以不恰当的方式访问它们会产生分段错误)。

但是,如果以不正确的方式访问我们自己的(进程)内存(例如尝试写入不可写入的空间),则会发生段错误。但最常见的原因是对的虚拟地址空间部分的访问没有将映射到物理地址空间。

而这一切都与虚拟内存系统有关。

+0

有了共享内存/内存映射文件,别人可能会混淆你的内存。在WIN32中也有类似'WriteProcessMemory'的令人讨厌的API! – paulm 2014-02-17 23:46:47

+1

@ paulm:是的,我知道。这就是我所想的“而像shmat这样的东西;) - 这些就是我所谓的”间接“访问。” – 2014-02-18 10:08:18

+0

在虚拟内存操作系统中,无法访问另一个进程虚拟内存的进程(通常,请操作系统实现者,请不要为此阻止我),而不是某种内存连接系统调用,它允许您访问。虚拟内存地址通常意味着不同的事情取决于正在考虑的过程。 – 2016-07-22 12:02:48

6

分段故障时的处理(运行的程序的实例)正试图访问发生只读正在使用由其它过程或访问不存在的(无效的)存储器地址的存储器地址或存储器范围。 悬空参考(指针)问题意味着试图访问一个对象或变量,其内容已被从存储器中删除,例如:

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

删除数组的正确方法是delete [] arr; – Damian 2016-03-28 15:48:06

9

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

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

编辑:

这里有一个参考:Segmentation fault by hardware

+1

有缺陷的RAM的快速和简单的测试是在一个循环中反复运行你的崩溃程序。如果程序没有内部的不确定性 - 也就是说,它总是为相同的输入产生相同的输出,或者至少它应该是这样 - 但是对于某些特定的输入,它会崩溃_sometimes_,并不总是但不是永远不会:那么你应该开始担心坏RAM。 – zwol 2017-10-26 21:39:35

4

维基百科的页面Segmentation_fault大约有它一个非常好的说明,只是指出了原因和理由。查看维基的详细描述。

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

以下是分段错误的一些常见原因:

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

反过来,这些常常引起导致无效的内存访问编程错误:解引用或分配到未初始化的指针(野生指针,它指向一个随机存储器地址)

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

  • 缓冲区溢出。

  • 堆栈溢出。

  • 试图执行不能正确编译的程序。 (一些编译器将输出一个可执行文件尽管编译时错误的存在。)当程序试图访问是不存在的一个存储器位置

0

段故障或访问冲突发生时,或试图访问以不允许的方式存储位置。

/* "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; 

这里我[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). 
+2

首先,seg故障与地址确实或不存在无关。这是关于你正在访问它,你不允许这样做。在你的特殊例子中,它甚至可以保证这个位置存在。由于该标准在数组情况下说,必须给出在其边界内的一个良好对齐数组上的指针pointg的有效地址**和**后面的1。 – dhein 2015-12-08 16:25:21

+0

它也与地址,如果你没有地址,如果你尝试访问这个地址,也有seg。故障。在我的例子中,这仅仅是为了理解的观点。 – 2015-12-12 03:11:06

15

虽然Zoul的回答解释了段错误是什么,我发现,这些种虫子可以特别拼命追赶,特别是如果你是新来的低像C++或C语言-level下面是一些常见的方式来获得一个段错误在你的程序:

不当格式控制字符串中printfscanf声明

格式控制字符串应该具有相同数量的转换说明(%s%d等)作为printfscanf具有参数要被打印或读取。这同样适用于fprintffscanf

不使用在参数&scanf

功能scanf采用作为参数的格式控制字符串和变量将在其中放置它在读取数据的地址。该&(地址)运算符用于提供变量的地址。

出界外数组引用

确保您没有违反您正在使​​用的任何数组的边界;即,您没有为数组下标数组,其值小于其最低元素的索引或大于其最高元素的索引。 Valgrind可以派上用场来检测这些参考 - 您可以使用valgrind--tool=exp-sgcheck标志。

访问未初始化指针

指针变量必须被访问之前被分配一个有效地址。确保你已经初始化所有的指针指向一个有效的内存区域。

使用不当&(地址)和*(解引用)运营商

您需要/使用这些的时候,尤其是在通过引用传递参数使用指针要小心。

shell限制

有时分割故障不是由程序中的错误引起的,但反而是造成系统内存限制被设置得太低。通常这是导致这种问题的堆栈大小的限制(堆栈溢出)。要检查内存限制,请使用bash中的ulimit命令。

使用gdb

您可以使用调试器gdb查看core文件的程序倾倒的回溯调试。每当程序段错误时,他们通常会在崩溃时将内存内容转储到core文件(core dumped)中。使用-g标志编译程序,运行于gdb并使用bt(回溯)。

2

简而言之:分段错误是操作系统发送信号给程序 ,说它检测到非法的内存访问并且过早地终止程序以防止内存被破坏。