细分故障常见原因的确定清单
注意:我们有很多段错误问题,基本上都有相同的答案,所以我试图将它们折叠成像我们未定义的参考那样的规范问题。
虽然我们有一个问题涉及分段错误,但它涵盖了什么 ,但没有列出很多原因。 最上面的答案是“有很多原因”,并且只列出一个,其他大部分答案都没有列出任何原因。
总而言之,我相信我们需要一个关于这个主题的组织良好的社区wiki ,其中列出了所有常见原因(然后列出一些)以获得段错误。 目的是帮助调试,正如答案的免责声明中所述。
我知道分段错误是什么,但在代码中很难发现它们常常看起来像什么。 毫无疑问,尽管无法详尽列举, 但 C和C ++ 中分段错误的最常见原因是什么?
警告!
以下是分段错误的潜在原因。 列举所有原因实际上是不可能的 。 此列表的目的是帮助诊断现有的段错误。
分段错误和未定义行为之间的关系不能太强调! 以下所有可能导致分段错误的情况在技术上都是未定义的行为。 这意味着他们可以做任何事情,而不仅仅是段错误 - 就像曾经在USENET上说过的那样,“编译器让魔鬼从你的鼻子里飞出来是合法的。” 不要指望每当你有未定义的行为时发生段错误。 您应该了解C和/或C ++中存在哪些未定义的行为,并避免编写具有这些行为的代码!
有关未定义行为的更多信息:
什么是Segfault?
简而言之,如果代码尝试访问没有权限访问的内存,则会导致分段错误。 每个程序都有一块内存(RAM)可供使用,出于安全原因,只允许访问该块中的内存。
有关分段故障的详细技术说明,请参阅什么是分段故障?
以下是分段错误最常见的原因。 再次, 这些应该用于诊断现有的段错误 。 要了解如何避免它们,请了解您的语言未定义的行为。
这个列表也不能替代你自己的调试工作 。 (请参阅答案底部的部分。)这些是您可以查找的内容,但您的调试工具是解决问题的唯一可靠方法。
访问一个NULL或未初始化的指针
如果你有一个指针是NULL( ptr=0
)或者是完全未初始化的指针(它现在还没有设置任何东西),试图访问或修改这个指针有未定义的行为。
int* ptr = 0;
*ptr += 5;
由于分配失败(例如使用malloc
或new
)将返回空指针,因此在使用它之前,应该始终检查指针是否为NULL。
还请注意,即使读取未初始化的指针(以及一般变量)的值(未取消引用)也是未定义的行为。
有时,未定义指针的访问可能非常微妙,例如试图将这样的指针解释为C打印语句中的字符串。
char* ptr;
sprintf(id, "%s", ptr);
也可以看看:
访问一个悬挂指针
如果您使用malloc
或new
分配内存,然后通过指针free
或delete
该内存,则该指针现在被视为悬挂指针 。 对它进行解引用(以及简单地读取它的值 - 如果你没有为它分配一些新的值,例如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
语句的简单省略,旨在防止函数中的无限递归。 该故事的寓意, 始终确保您的错误检查工作!
也可以看看:
野指针
在内存中创建一个随机位置的指针就像是用你的代码来玩俄罗斯轮盘赌 - 你很容易错过并创建一个指向你没有访问权限的位置的指针。
int n = 123;
int* ptr = (&n + 0xDEADBEEF); //This is just stupid, people.
通常,不要创建指向文字内存位置的指针。 即使他们工作一次,下次他们可能不会。 你无法预测你的程序的内存在哪里执行。
也可以看看:
尝试读取数组的末尾
数组是连续的内存区域,每个连续的元素都位于内存中的下一个地址处。 但是,大多数阵列并不具有它们有多大的先天感,或者最后一个元素是什么。 因此,很容易吹到数组的末尾,而永远不会知道它,特别是如果您使用指针算术。
如果读过数组的末尾,则可能会发生未初始化或属于其他事情的内存。 这在技术上是未定义的行为 。 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);
此外,应该注意的是,甚至不允许创建(更不用说提取引用)指向数组外部的指针(只有指向数组内的元素或指向数组内的元素时,才可以创建此类指针)。 否则,你触发未定义的行为。
也可以看看:
忘记C字符串上的NUL终止符。
C字符串本身就是具有一些额外行为的数组。 它们必须是空终止的,这意味着它们最后有一个