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

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

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

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

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


警告!

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

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

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

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

  • 什么是Segfault?

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

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

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

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


    访问一个NULL或未初始化的指针

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

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

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

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

    有时,未定义指针的访问可能非常微妙,例如试图将这样的指针解释为C打印语句中的字符串。

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

    也可以看看:

  • 如何在C中检测变量uninitialized / catch段错误
  • 字符串和int的连接导致seg故障C

  • 访问一个悬挂指针

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

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

    也可以看看:

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

  • 堆栈溢出

    [不,不是你现在所在的网站,是以什么名字命名的。]简而言之,“堆栈”就像你在某些用餐者那里粘贴你的订单。 这个问题可能会发生,当你把太多的订单放在那个钉子上,可以这么说。 在计算机中,任何未动态分配的变量以及尚未由CPU处理的任何命令都将进入堆栈。

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

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

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

    int stupidArray[600851475143];
    

    一个在野外堆栈溢出的案例来自一个条件中的return语句的简单省略,旨在防止函数中的无限递归。 该故事的寓意, 始终确保您的错误检查工作!

    也可以看看:

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

  • 野指针

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

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

    通常,不要创建指向文字内存位置的指针。 即使他们工作一次,下次他们可能不会。 你无法预测你的程序的内存在哪里执行。

    也可以看看:

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

    或频繁使用看到一个for<=代替< (读取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);
    

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

    也可以看看:

  • 我有segfaults!

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

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

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

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

    对于C字符串, 是否会产生任何影响是真正的难题。 你应该假设它会避免未定义的行为。


    尝试修改字符串文字

    如果将字符串文字分配给char *,则不能修改它。 例如...

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

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

    也可以看看:

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

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

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

    也可以看看:

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

  • 工具链中的错误。

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

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

    也就是说, 除非事实证明你应该始终认为问题是你。


    其他原因

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

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

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

  • 调试

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

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

    一些内存调试器

  • GDB | Mac,Linux
  • valgrind(memcheck)| Linux的
  • Dr. Memory | 视窗
  • 此外,建议使用静态分析工具来检测未定义的行为 - 但同样,它们只是一种工具,仅用于帮助您查找未定义的行为,并不保证能够查找所有未定义行为的发生。

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

    链接地址: http://www.djcxy.com/p/7629.html

    上一篇: Definitive List of Common Reasons for Segmentation Faults

    下一篇: Weird profiling output using gprof