Cod*_*e92 56 c c++ segmentation-fault
注意:我们有很多段错误问题,答案基本相同,所以我试图将它们折叠成一个规范的问题,就像我们对未定义的引用一样.
虽然我们有一个问题涉及分段错误是什么,但它涵盖了什么,但没有列出很多原因.最佳答案说"有很多原因",只列出一个,其他大多数答案都没有列出任何理由.
总而言之,我认为我们需要一个组织良好的社区wiki来讨论这个主题,它列出了所有常见原因(然后是一些)来获取段错误.目的是帮助调试,如答案的免责声明中所述.
我知道什么是分段错误,但是在不知道它们通常是什么样的情况下很难发现代码.虽然毫无疑问,有太多的内容无法详尽列出,但 C和C++ 中分段错误的最常见原因是什么?
Cod*_*e92 68
警告!
以下是分段错误的潜在原因.列出所有原因几乎是不可能的.此列表的目的是帮助诊断现有的段错误.
分割故障和未定义行为之间的关系,不能强调不够!以下所有可能产生分段错误的情况都是技术上未定义的行为.这意味着他们可以做任何事情,而不仅仅是段错误 - 就像有人曾经在USENET上说的那样," 编译器让恶魔飞出你的鼻子是合法的. " 每当您有未定义的行为时,不要指望发生段错误.您应该了解C和/或C++中存在哪些未定义的行为,并避免编写具有它们的代码!
有关未定义行为的更多信息:
简而言之,当代码尝试访问它没有访问权限的内存时,会导致分段错误.每个程序都有一块内存(RAM)可供使用,出于安全考虑,只允许访问该块中的内存.
有关分段错误什么是更彻底的技术解释是,看什么是分段故障?.
以下是分段错误错误的最常见原因.同样,这些应该用于诊断现有的段错误.要学习如何避免它们,请学习语言的未定义行为.
此列表也不能替代您自己的调试工作.(请参阅答案底部的该部分.)这些是您可以查找的内容,但您的调试工具是解决问题的唯一可靠方法.
如果你有一个NULL(ptr=0
)指针或完全未初始化的指针(它根本没有设置为任何东西),尝试使用该指针访问或修改具有未定义的行为.
int* ptr = 0;
*ptr += 5;
Run Code Online (Sandbox Code Playgroud)
由于失败的分配(例如with malloc
或new
)将返回空指针,因此在使用它之前应始终检查指针是否为NULL.
另请注意,即使读取未初始化指针(以及通常的变量)的值(不解除引用)也是未定义的行为.
有时这种对未定义指针的访问可能非常微妙,例如在尝试将这样的指针解释为C打印语句中的字符串时.
char* ptr;
sprintf(id, "%s", ptr);
Run Code Online (Sandbox Code Playgroud)
也可以看看:
如果您使用malloc
或new
分配内存,然后通过指针稍后free
或delete
该内存,该指针现在被视为悬空指针.取消引用它(以及简单地读取它的值 - 授予你没有为它分配一些新值,如NULL)是未定义的行为,并且可能导致分段错误.
Something* ptr = new Something(123, 456);
delete ptr;
std::cout << ptr->foo << std::endl;
Run Code Online (Sandbox Code Playgroud)
也可以看看:
[不,不是你现在所在的网站,是什么被命名.]过度简化,"堆栈"就像你在一些食客中粘贴你的订单纸.当您在该峰值上放置太多订单时,可能会发生此问题.在计算机中,任何未动态分配的变量以及尚未由CPU处理的任何命令都会进入堆栈.
造成这种情况的一个原因可能是深度或无限递归,例如当函数调用自身而无法停止时.因为那个堆栈已经溢出,订单文件开始"脱落"并占用其他不适合他们的空间.因此,我们可以得到分段错误.另一个原因可能是尝试初始化一个非常大的数组:它只是一个单一的顺序,但它本身就足够大了.
int stupidFunction(int n)
{
return stupidFunction(n);
}
Run Code Online (Sandbox Code Playgroud)
堆栈溢出的另一个原因是一次有太多(非动态分配)变量.
int stupidArray[600851475143];
Run Code Online (Sandbox Code Playgroud)
在野外堆栈溢出的一种情况来自return
于条件中的语句的简单省略,旨在防止函数中的无限递归.这个故事的寓意,始终确保您的错误检查工作!
也可以看看:
在内存中创建一个指向某个随机位置的指针就像用你的代码玩俄罗斯轮盘赌 - 你很容易错过并创建一个指向你没有访问权限的位置的指针.
int n = 123;
int* ptr = (&n + 0xDEADBEEF); //This is just stupid, people.
Run Code Online (Sandbox Code Playgroud)
作为一般规则,不要创建指向文字内存位置的指针.即使他们工作一次,下次他们可能也不会.在任何给定的执行中,您都无法预测程序的内存在何处.
也可以看看:
数组是连续的内存区域,其中每个连续元素位于内存中的下一个地址.但是,大多数数组并不具有它们有多大,或者最后一个元素的内在含义.因此,很容易超越数组的末尾并且永远不会知道它,特别是如果你使用指针算法.
如果你读过数组的末尾,你可能会进入未初始化或属于其他东西的内存.这是技术上未定义的行为.段错误只是众多潜在的未定义行为之一.[坦率地说,如果你在这里遇到段错误,那你很幸运.其他人更难诊断.]
// 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++;
}
Run Code Online (Sandbox Code Playgroud)
或者经常看到的for
用<=
而不是<
(读取1个字节太多):
char arr[10];
for (int i = 0; i<=10; i++)
{
std::cout << arr[i] << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
甚至是一个不好的拼写错误,编译得很好(在这里看到)并且只分配了1个用元素dim
而不是元素初始化的dim
元素.
int* my_array = new int(dim);
Run Code Online (Sandbox Code Playgroud)
此外,应该注意的是,甚至不允许创建(更不用说解除引用)指向数组外部的指针(只有当指向数组中的元素或者指向结束时的元素时,才可以创建此类指针).否则,您将触发未定义的行为.
也可以看看:
C字符串本身就是具有一些额外行为的数组.它们必须为空终止,这意味着它们\0
最后会被可靠地用作字符串.这在某些情况下会自动完成,而在其他情况下则不会.
如果忘记这一点,一些处理C字符串的函数永远不知道何时停止,并且您可以获得与读取数组末尾相同的问题.
char str[3] = {'f', 'o', 'o'};
int i = 0;
while(str[i] != '\0')
{
std::cout << str[i] << std::endl;
i++;
}
Run Code Online (Sandbox Code Playgroud)
使用C字符串,它是否\0
会有所不同.你应该假设它会避免未定义的行为:所以写得更好char str[4] = {'f', 'o', 'o', '\0'};
如果将字符串文字指定给char*,则无法修改它.例如...
char* foo = "Hello, world!"
foo[7] = 'W';
Run Code Online (Sandbox Code Playgroud)
...触发未定义的行为,分段错误是一种可能的结果.
也可以看看:
您必须使用malloc
和free
在一起,new
和delete
在一起,new[]
并delete[]
一起.如果你混淆了,你可能会遇到段错误和其他奇怪的行为.
也可以看看:
编译器的机器代码后端中的错误非常能够将有效代码转换为segfaults的可执行文件.链接器中的错误也可以做到这一点.
特别可怕的是,这不是由您自己的代码调用的UB.
也就是说,除非另有证明,否则你应该始终认为问题就在于此.
分段错误的可能原因与未定义行为的数量一样多,甚至列出的标准文档也有很多.
一些不太常见的原因需要检查:
调试工具有助于诊断段错误的原因.使用调试标志(-g
)编译程序,然后使用调试器运行它以查找可能发生段错误的位置.
最近的编译器支持构建-fsanitize=address
,这通常会导致程序运行速度减慢约2倍,但可以更准确地检测地址错误.但是,此方法不支持其他错误(例如从未初始化的内存读取或泄漏非内存资源(如文件描述符)),并且不可能同时使用许多调试工具和ASan.
一些内存调试器
此外,建议使用静态分析工具来检测未定义的行为 - 但同样,它们只是帮助您查找未定义行为的工具,并且它们不保证找到所有出现的未定义行为.
但是,如果你真的不走运,使用调试器(或者,更少见,只需重新编译调试信息)可能会充分影响程序的代码和内存,从而不再发生段错误,这种现象称为heisenbug.
归档时间: |
|
查看次数: |
8623 次 |
最近记录: |