分割故障常见原因的确定清单

注:我们有很多段错误的问题,基本上是相同的答案,所以我试图将它们折叠成一个规范的问题,就像我们有未定义的参考 。

虽然我们有一个问题涉及到细分故障是 什么 ,它涵盖了什么 ,但没有列出很多原因。 最上面的答案是“有很多原因”,只列出一个,其他大部分答案都没有列出任何理由。

总而言之,我相信我们需要一个关于这个主题的组织良好的社区维基 ,其中列出了所有常见原因(然后是一些)来获得段错误。 目的是帮助debugging,正如答案的免责声明中所述。

我知道分段错误是什么,但在代码中很难发现它们常常看起来像什么。 毫无疑问,尽pipe无法详尽地列出, C和C ++ 中分段错误的最常见原因是什么?

警告!

以下是分段错误的潜在原因。 列举所有原因几乎是不可能的 。 此列表的目的是帮助诊断现有的段错误。

分段错误和未定义行为之间的关系不能被强调得太多 以下所有可能造成分段故障的情况在技术上都是未定义的行为。 这意味着他们可以做任何事情 ,而不仅仅是段错误 – 正如曾经在USENET上所说的那样,“ 编译器让魔鬼从你的鼻子里飞出来是合法的 ”。 不要指望每当你有未定义的行为发生段错误。 您应该了解C和/或C ++中存在哪些未定义的行为,并避免编写具有这些行为的代码!

有关未定义行为的更多信息:

  • C中产生Segfault的最简单的标准符合方式是什么?
  • 未定义,未指定和实现定义的行为
  • 未定义的行为如何未定义?

什么是Segfault?

简而言之,当代码尝试访问没有权限访问的内存时,会导致分段错误。 每个程序都有一块内存(RAM)可供使用,出于安全原因,只允许访问该块中的内存。

有关分段故障的详细技术说明,请参阅什么是分段故障?

以下是分段错误最常见的原因。 再次, 这些应该用于诊断现有的段错误 。 要学习如何避免它们,学习你的语言的未定义的行为

这个列表也不能替代您自己的debugging工作 。 (请参阅答案底部的部分。)这些是您可以查找的内容,但是您的debugging工具是解决问题的唯一可靠方法。


访问NULL或未初始化的指针

如果你的指针是NULL( ptr=0 )或者是完全未初始化的(它现在还没有被设置成任何东西),试图访问或者使用该指针修改的行为是未定义的。

 int* ptr = 0; *ptr += 5; 

由于分配失败(例如mallocnew )将返回一个空指针,因此在使用它之前,应该始终检查指针是否为NULL。

还要注意的是,即使读取未初始化的指针(和一般variables)的值(不解除引用)也是未定义的行为。

有时这个未定义指针的访问可能非常微妙,比如试图在C print语句中将这样的指针解释为一个string。

 char* ptr; sprintf(id, "%s", ptr); 

也可以看看:

  • 如何检测variables未初始化/ catch在C中的错误
  • string和int的连接导致seg故障C

访问一个悬挂指针

如果使用mallocnew分配内存,然后通过指针freedelete内存,则该指针现在被视为悬挂指针 。 解引用它(以及简单地读取它的值 – 如果你没有给它分配一些新的值,如NULL)是未定义的行为,并可能导致分段错误。

 Something* ptr = new Something(123, 456); delete ptr; std::cout << ptr->foo << std::endl; 

也可以看看:

  • 什么是悬挂指针?
  • 为什么我的悬挂指针不会导致分段错误?

堆栈溢出

[不,不是你现在的网站,是什么命名的。]简而言之,“堆栈”就像你在一些食客那里粘贴你的订单。 这个问题可能会发生,当你把太多的订单在这个秒杀,可以这么说。 在计算机中,任何未被dynamic分配的variables以及尚未由CPU处理的任何命令都将进入堆栈。

造成这种情况的一个原因可能是深层recursion或无限recursion,例如当一个函数自己调用无法停止的时候。 由于该堆栈已经溢出,订单文件开始“脱落”并占用其他空间不是他们的意思。 因此,我们可以得到一个分割错误。 另一个原因可能是尝试初始化一个非常大的数组:它只是一个单一的顺序,而是一个已经足够大的自己。

 int stupidFunction(int n) { return stupidFunction(n); } 

堆栈溢出的另一个原因是一次有太多的(非dynamic分配的)variables。

 int stupidArray[600851475143]; 

一个在野外堆栈溢出的情况来自于简单的在条件中省略return语句以防止函数中的无限recursion。 这个故事的道德, 总是确保你的错误检查工作!

也可以看看:

  • 在C语言中创build大型数组时出现分段错误
  • 当初始化数组时,Seg错误

野指针

在内存中创build一个随机位置的指针就像是用你的代码来玩俄罗斯轮盘赌 – 你可能会错过并创build一个指向你没有访问权限的位置的指针。

 int n = 123; int* ptr = (&n + 0xDEADBEEF); //This is just stupid, people. 

通常,不要创build指向文字内存位置的指针。 即使他们工作一次,下一次他们可能不会。 你不能预测你的程序的内存将在任何给定的执行。

也可以看看:

  • C中的“野指针”是什么意思?

试图读取数组的末尾

一个数组是连续的内存区域,每个连续的元素都位于内存中的下一个地址。 然而,大多数数组并不知道它们有多大,或者最后一个元素是什么。 因此,很容易吹到数组的末尾,而永远不知道它,特别是如果你使用指针算术。

如果读过数组的末尾,则可能会进入未初始化的内存或属于其他内存的内存。 这在技术上是不确定的行为 。 segfault只是许多潜在的未定义行为之一。 [坦白说,如果你在这里遇到段错误,那么你很幸运。 其他人难以诊断。]

 // like most UB, this code is a total crapshoot. int arr[3] {5, 151, 478}; int i = 0; while(arr[i] != 16) { std::cout << arr[i] << std::endl; i++; } 

或者用<=代替< (读取1个字节太多)的经常看到的:

 char arr[10]; for (int i = 0; i<=10; i++) { std::cout << arr[i] << std::endl; } 

甚至是一个不幸的错字编译好(见这里 ),并分配只有一个元素初始化与dim而不是dim元素。

 int* my_array = new int(dim); 

此外,应该注意的是,甚至不允许创build(更不用说解除引用)指向数组外部的指针(只有指向数组内的元素或指向数组内的元素时,才可以创build这样的指针)。 否则,你触发了未定义的行为。

也可以看看:

  • 我有segfaults!

忘记Cstring上的NUL终止符。

Cstring本身就是具有一些额外行为的数组。 它们必须是空终止的,这意味着它们最后有一个\0 ,可以可靠地用作string。 这是在某些情况下自动完成的,而不是在其他情况下完成。

如果忘记了这一点,一些处理Cstring的函数永远不会知道何时停止,并且可以像读取数组末尾一样获得相同的问题。

 char str[3] = {'f', 'o', 'o'}; int i = 0; while(str[i] != '\0') { std::cout << str[i] << std::endl; i++; } 

用Cstring, \0是否会产生任何影响是真正的难题。 你应该假设它会避免未定义的行为。


试图修改一个string文字

如果将string文字分配给char *,则不能修改。 例如…

 char* foo = "Hello, world!" foo[7] = 'W'; 

…触发未定义的行为 ,并且分段错误是一个可能的结果。

也可以看看:

  • 为什么这个string反转C代码导致分段错误?

不匹配分配和释放方法

你必须一起使用mallocfreenewdelete在一起,并且new[]delete[]一起使用。 如果你混合起来,你可以得到segfaults和其他奇怪的行为。

也可以看看:

  • 使用C ++删除malloc的行为
  • 分段错误(核心转储)当我删除指针

工具链中的错误。

编译器的机器代码后端中的一个错误能够将有效的代码转换为段错误的可执行文件。 链接器中的错误肯定也能做到这一点。

特别可怕的是,这不是由你自己的代码调用UB。

也就是说, 除非事实certificate,否则你应该总是认为问题是你的。


其他原因

分段错误的可能原因大约与未定义行为的数量一样多,即使是标准文档列出也有太多。

一些不太常见的原因要检查:

  • 由于其他UB在某些平台上生成的UD2
  • 被删除的条目上完成的c ++ STL map :: operator []

debugging

debugging工具有助于诊断段错误的原因。 用debugging标志( -g )编译你的程序,然后用你的debugging程序运行它,找出可能发生段错误的地方。

最近的编译器支持使用-fsanitize=address构build,这通常会导致运行速度慢大约2 -fsanitize=address程序,但可以更准确地检测到地址错误。 但是,这种方法不支持其他错误(如从未初始化的内存读取或泄漏非内存资源,例如文件描述符),并且不可能同时使用许多debugging工具和ASan 。

一些内存debugging器

  • GDB | Mac,Linux
  • valgrind(memcheck)| Linux的
  • Dr. Memory | 视窗

此外,build议使用静态分析工具来检测未定义的行为 – 但是,再次,它们仅仅是帮助您find未定义行为的工具,并不能保证find所有未定义行为。

如果你真的很不幸,使用一个debugging器(或者更less的情况下,只是重新编译debugging信息)可能会充分影响程序的代码和内存,导致段错误不再发生,这种现象称为heisenbug