修复C++中的分段错误

134

我正在为Windows和Unix编写跨平台的C++程序。在Windows方面,代码可以编译和执行没有问题。但是在Unix方面,它可以编译,但是当我尝试运行它时,会出现段错误。我的第一个想法是指针存在问题。

有哪些好的方法可以找到并解决段错误?

6个回答

192
  1. 使用-g编译你的应用程序,这样在二进制文件中就会有调试符号。

  2. 使用gdb打开gdb控制台。

  3. 在控制台中使用file并传入你的应用程序的二进制文件。

  4. 使用run并传入应用程序启动所需的任何参数。

  5. 执行某些操作以导致分段错误

  6. gdb控制台中输入bt,以获取分段错误的堆栈跟踪信息。


1
在CMake的上下文中,使用g编译意味着什么? - Schütze
7
启用调试版本构建类型。其中一种方法是使用命令 cmake -DCMAKE_BUILD_TYPE=Debug - Antonin Décimo
1
你能通过运行 gdb <filename> 来完成第2步和第3步吗? - RARA
@RAFA,即如果您的程序通过./main 1 2 3运行,则首先输入:“gdb”以进入gdb提示符,然后输入“file main”,最后输入“run 1 2 3”。 - Edison Lo
在我的情况下,每次启动时并不总是出现分段错误,只是有时候会出现。有没有办法自动化这个检查过程,以便找到确实在启动时出现分段错误的情况?我尝试按照您的步骤进行迭代,但这非常繁琐。 - cangozpi

44
有时候,崩溃本身并不是问题的真正原因——也许内存在早期就被破坏了,但需要一段时间才能显示出来。查看valgrind,它有很多检查指针问题的方法(包括数组边界检查)。它将告诉您问题从哪里开始,而不仅仅是崩溃发生的行。

23

在问题出现之前,尽可能避免它:

  • 尽可能频繁地编译和运行你的代码,这样会更容易找到错误所在。
  • 尝试封装低级别/易出错的例程,以便很少直接使用内存(注意程序模型)。
  • 维护一个测试套件。了解当前工作情况、不再工作情况等概述,将帮助您找到问题所在(Boost 测试是一个可能的解决方案,我自己不用它,但文档可以帮助理解必须显示哪种信息)。

使用适当的调试工具。在 Unix 上:

  • GDB 可以告诉你程序崩溃的位置,并让你查看上下文。
  • Valgrind 将帮助你检测到许多与内存相关的错误。
  • 使用 GCC 你还可以使用 mudflap 使用 GCC、Clang 以及从十月开始实验性的 MSVC,你可以使用 Address/Memory Sanitizer。它可以检测到一些 Valgrind 检测不到的错误,并且性能损失更小。通过使用-fsanitize=address标志进行编译。

最后,我建议做一些常规的事情。你的程序越可读性、可维护性、清晰和整洁,调试就会越容易。


7
在Unix系统中,你可以使用免费且功能强大的valgrind来查找问题。如果你想自己处理问题,可以重载newdelete运算符来设置一个配置,其中每个新对象之前和之后都有一个带有0xDEADBEEF的1字节。然后跟踪每次迭代发生的情况。这种方法可能无法捕捉到所有问题(你甚至不能保证会接触到那些字节),但在过去的Windows平台上对我起过作用。

1
好的,这将是4个字节而不是1个...但原则是正确的。 - Jonas Wagner
1
我可以链接到我的非侵入式堆调试器吗? :-) - fredoverflow
加油吧。我们这里都是关于帮助他人的,所以任何能够帮助的东西都应该被添加进来。 - wheaties
虽然重载 newdelete 可以非常有用,但使用 -fsanitize=address 是更好的选择,因为编译器将在运行时检测问题并自动将内存转储到屏幕上,这使得调试变得更加容易。 - Tarick Welling
除了 newdelete,如果你使用的是 gcc,你还可以包装 malloc。请参见 --wrap=symbol。我将在发布代码中执行此操作,以便获得一些运行时诊断信息。 - Daisuke Aramaki

4

是的,指针存在问题。很可能您正在使用未正确初始化的指针,但也有可能您在双重释放等内存管理方面出现了问题。

为避免本地变量作为未初始化的指针,请尽可能晚地声明它们,最好(并非总是可行)在可以使用有意义的值进行初始化时声明它们。通过检查代码来确信在使用它们之前它们将具有值。如果您在这方面遇到困难,请将它们初始化为空指针常量(通常写为NULL0),然后检查它们。

为避免成员变量作为未初始化的指针,请确保在构造函数中正确初始化它们,并在复制构造函数和赋值运算符中正确处理它们。不要依赖于init函数进行内存管理,尽管您可以使用它进行其他初始化。

如果您的类不需要复制构造函数或赋值运算符,则可以将它们声明为私有成员函数并从未定义它们。如果显式或隐式使用它们,这将导致编译器错误。

在适用的情况下,请使用智能指针。这里的一个重要优点是,如果您坚持使用它们并始终使用它们,则可以完全避免编写delete,也不会出现双重删除。

尽可能使用C++字符串和容器类,而不是C风格的字符串和数组。考虑使用.at(i)而不是[i],因为这将强制进行边界检查。请查看您的编译器或库是否可以在调试模式下设置为检查[i]的边界。缓冲区溢出可能会导致段错误,从而在完全正确的指针上写入垃圾。

执行这些操作将大大降低段错误和其他内存问题的可能性。它们无疑不能解决所有问题,这就是为什么当您没有问题时应使用valgrind,有问题时应同时使用valgrind和gdb。


1

我不知道有任何方法可以用来修复这种情况。我认为也不可能想出一个,因为你的程序行为未定义(我不知道在什么情况下没有由某种UB导致SEGFAULT)。

在问题出现之前,有各种“方法”可以避免这个问题。其中一个重要的是RAII。

除此之外,你只需要把你最好的能量投入到它中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接