c++ - 段错误常见原因的最终列表

标签 c++ c segmentation-fault

NOTE: We have a lot of segfault questions, with largely the same answers, so I'm trying to collapse them into a canonical question like we have for undefined reference.

Although we have a question covering what a segmentation fault is, it covers the what, but doesn't list many reasons. The top answer says "there are many reasons", and only lists one, and most of the other answers don't list any reasons.

All in all, I believe we need a well-organized community wiki on this topic, which lists all the common causes (and then some) to get segfaults. The purpose is to aid in debugging, as mentioned in the answer's disclaimer.



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

最佳答案

WARNING!

The following are potential reasons for a segmentation fault. It is virtually impossible to list all reasons. The purpose of this list is to help diagnose an existing segfault.

The relationship between segmentation faults and undefined behavior cannot be stressed enough! All of the below situations that can create a segmentation fault are technically undefined behavior. That means that they can do anything, not just segfault -- as someone once said on USENET, "it is legal for the compiler to make demons fly out of your nose.". Don't count on a segfault happening whenever you have undefined behavior. You should learn which undefined behaviors exist in C and/or C++, and avoid writing code that has them!

More information on Undefined Behavior:



什么是段错误?

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

有关什么是段错误的更详尽的技术解释,请参阅 What is a segmentation fault?

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

这个列表也是 没有替代做你自己的调试工作 。 (请参阅答案底部的那部分。)这些是您可以寻找的东西,但您的调试工具是唯一可靠的方法来解决问题。

访问 NULL 或未初始化的指针

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

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

另请注意,即使读取未初始化的指针(以及一般的变量)的值(不取消引用)也是未定义的行为。

有时,对未定义指针的访问可能非常微妙,例如在尝试将此类指针解释为 C 打印语句中的字符串时。
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表示内存通过指针,那个指针现在被认为是1910x1421421412141214214121412141214141214141214121421214141之间的指针取消引用它(以及简单地读取它的值 - 假设您没有为其分配一些新值,例如 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 字符串本身就是具有一些附加行为的数组。它们必须以空字符结尾,这意味着它们在末尾有一个 \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

    在这种情况下,您可能想要做的是获取核心转储,并使用调试器获取回溯。
  • How to generate a core dump in Linux on a segmentation fault?
  • How do I analyse a program's core dump file with GDB when it has command-line parameters?
  • 关于c++ - 段错误常见原因的最终列表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33047452/

    相关文章:

    使用 dowhile 进行 C++ 验证

    c++ - 检查字符串是否为 int 的函数不起作用

    c - 如何从 Octave 或 Matlab 中的文件加载 C 结构体

    c++ - 不知道段错误发生在哪里

    c++ - C++中的初始化列表名称冲突

    c++ - C++ 中的 SQLite SELECT JOIN 和 VIEWS

    c - 尝试将结构的内部数组设置为在 C 中初始化

    c - GCC 关于用数组分配指针变量的警告?

    c++ - 带有调试输出的 X3 解析器段错误 (BOOST_SPIRIT_X3_DEBUG)

    c - C 函数中的 SIGSEGV