注意:我们有很多段错误,基本相同
  答案,所以我试图将其分解为一个规范的问题,例如
  我们有undefined reference
  
  尽管我们有一个关于what a segmentation fault is的问题,但它涵盖了什么,但没有列出许多原因。最上面的答案说“原因很多”,只列出了一个,其他大多数答案都没有列出任何原因。
  
  总而言之,我相信我们需要一个组织良好的社区Wiki,以讨论该主题,其中列出了导致段错误的所有常见原因(然后列出了某些原因)。如答案的免责声明中所述,目的是帮助调试。


我知道分段错误是什么,但是在不知道它们通常是什么样的情况下很难在代码中发现。毫无疑问,尽管有太多的东西无法详尽列出,但是C和C ++中最常见的分段错误原因是什么?

最佳答案

警告!
  
  以下是分段错误的潜在原因。列出所有原因实际上是不可能的。此列表的目的是帮助诊断现有的段错误。
  
  分割错误和不确定行为之间的关系无法得到足够的重视!以下所有可能造成分段错误的情况在技术上都是未定义的行为。这意味着他们可以做任何事情,而不仅仅是段错误-正如有人曾经在USENET上说的“ it is legal for the compiler to make demons fly out of your nose.”。当您有未定义的行为时,不要指望发生段错误。您应该了解C和/或C ++中存在哪些未定义的行为,并避免编写包含它们的代码!
  
  有关未定义行为的更多信息:
  
  
  What is the simplest standard conform way to produce a Segfault in C?
  Undefined, unspecified and implementation-defined behavior
  How undefined is undefined behavior?
  




什么是段故障?

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

有关什么是分段错误的更详尽的技术说明,请参见What is a segmentation fault?

这是分段错误错误的最常见原因。同样,这些应用于诊断现有的段错误。若要了解如何避免它们,请学习您语言未定义的行为。

此列表也不能代替您自己进行调试。 (请参阅答案底部的那部分。)您可以寻找这些内容,但是调试工具是唯一解决问题的可靠方法。



访问NULL或未初始化的指针

如果您使用的指针为NULL(ptr=0)或完全未初始化(尚未设置为任何指针),则尝试使用该指针进行访问或修改具有未定义的行为。

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


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

还要注意,即使读取未初始化的指针(通常不包括变量)的值(不进行取消引用)也是未定义的行为。

有时,对未定义指针的这种访问可能非常微妙,例如试图在C print语句中将此类指针解释为字符串。

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


也可以看看:


How to detect if variable uninitialized/catch segfault in C
Concatenation of string and int results in seg fault C




访问悬空指针

如果使用mallocnew分配内存,然后使用指针通过freedelete分配该内存,则现在将该指针视为悬空指针。取消引用它(以及简单地读取它的值-授予您未为其分配一些新值,例如NULL)是未定义的行为,并且可能导致分段错误。

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


也可以看看:


What is a dangling pointer?
Why my dangling pointer doesn't cause a segmentation fault?




堆栈溢出

[不,不是您现在所在的站点,而是它的名字。]过于简单,“堆栈”就像您在某些用餐者身上贴订单纸的尖峰。可以说,当您在该峰值上放置太多订单时,可能会发生此问题。在计算机中,任何未动态分配的变量以及尚未由CPU处理的任何命令都将进入堆栈。

造成这种情况的一个原因可能是深度递归或无限递归,例如当函数调用自身而无法停止时。由于该堆栈已溢出,定单纸开始“掉落”,并占用了其他不适用于它们的空间。因此,我们可以得到一个分割错误。另一个原因可能是试图初始化一个非常大的数组:这只是一个单,但它本身已经足够大。

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


堆栈溢出的另一个原因是一次有太多(非动态分配的)变量。

int stupidArray[600851475143];


狂放的堆栈溢出的一种情况是在条件下简单地省略了return语句,该条件旨在防止函数中的无限递归。这个故事的寓意在于,始终确保您的错误检查工作正常!

也可以看看:


Segmentation Fault While Creating Large Arrays in C
Seg Fault when initializing array




野指针

在内存中创建指向某个随机位置的指针就像用代码玩俄罗​​斯轮盘赌一样-您很容易错过并创建一个指向您无权访问的位置的指针。

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


通常,请勿创建指向文字存储位置的指针。即使他们一次工作,下次也可能没有。您无法预测任何给定执行时程序存储器的位置。

也可以看看:


What is the meaning of "wild pointer" in C?




尝试读取数组末尾的内容

数组是内存的连续区域,其中每个连续的元素都位于内存中的下一个地址。但是,大多数数组对它们的大小或最后一个元素没有先天的感觉。因此,很容易遗漏数组的末尾而永远不知道它,尤其是在使用指针算术的情况下。

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

// 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++;
}


或经常使用for<=而不是<的人(读太多1个字节):

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


甚至是运气不好的错字,它会编译良好(见here)并仅分配1个用dim初始化的元素而不是dim元素。

int* my_array = new int(dim);


另外应注意,甚至不允许创建(更不用说取消引用)指向数组外部的指针(仅当该指针指向数组中的一个元素或指向末尾的一个元素时,才可以创建此类指针)。否则,您将触发未定义的行为。

也可以看看:


I have segfaults!




忘记C字符串上的NUL终止符。

C字符串本身就是具有一些其他行为的数组。它们必须以null终止,这意味着它们的末尾带有\0,才能可靠地用作字符串。在某些情况下会自动执行此操作,而在其他情况下则不会自动执行。

如果忘记了这一点,则处理C字符串的某些函数将永远不知道何时停止,并且您可能会遇到与读取数组末尾相同的问题。

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


对于C字符串,\0是否会有所不同确实是一招两用。您应该假设它将避免未定义的行为:所以最好写char str[4] = {'f', 'o', 'o', '\0'};



尝试修改字符串文字

如果将字符串文字分配给char *,则无法修改。例如...

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


...触发不确定的行为,而分段错误是一种可能的结果。

也可以看看:


Why is this string reversal C code causing a segmentation fault?




不匹配的分配和解除分配方法

您必须同时使用mallocfree,同时使用newdelete,以及同时使用new[]delete[]。如果将它们混在一起,则会出现段错误和其他奇怪的行为。

也可以看看:


Behaviour of malloc with delete in C++
Segmentation fault (core dumped) when I delete pointer




工具链中的错误。

编译器的机器代码后端中的错误非常有能力将有效代码转换为存在段错误的可执行文件。链接器中的错误肯定也可以这样做。

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

就是说,除非另有说明,否则您应该始终假设问题出在您身上。



其他原因

分段错误的可能原因与未定义行为的数量一样多,甚至连标准文档也无法列出。

一些不常见的检查原因:


UD2 generated on some platforms due to other UB
c++ STL map::operator[] done on an entry being deleted




调试

调试工具有助于诊断段错误的原因。使用调试标志(-g)编译程序,然后使用调试器运行该程序以查找可能发生段错误的位置。

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

一些内存调试器


GDB | Mac,Linux
valgrind(memcheck)|的Linux
记忆博士|视窗


另外,建议使用静态分析工具来检测未定义的行为-但是,它们再次只是为了帮助您发现未定义的行为,并且不能保证找到所有出现的未定义的行为的工具。

但是,如果您真的很倒霉,那么使用调试器(或者,很少使用调试信息进行重新编译)可能会充分影响程序的代码和内存,从而使段错误不再发生,这种现象称为heisenbug

关于c++ - segmentation 故障的常见原因的明确列表,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/59992067/

10-16 04:15