136 c c++ debugging segmentation-fault
我们如何确定导致分段错误的代码中的错误在哪里?
在编写了一些代码后,为了确定我在哪里有分段错误,我的编译器(gcc)能告诉我程序中的错误位置吗?
nc3*_*c3b 190
海湾合作委员会不能这样做,但GDB肯定可以.使用-g开关编译程序,如下所示:
gcc program.c -g
然后使用gdb:
$ gdb ./a.out
(gdb) run
<segfault happens here>
(gdb) backtrace
<offending code is shown here>
这是一个很好的教程,可以帮助您开始使用GDB.
小智 37
此外,您可以尝试Valgrind:如果您安装Valgrind并运行valgrind --leak-check = full,那么它将运行您的程序并显示任何段错误的堆栈跟踪,以及任何无效的内存读取或写入以及内存泄漏.这真的很有用.
Luc*_*cas 17
您还可以使用核心转储,然后使用gdb进行检查.要获得有用的信息,还需要使用-g标志进行编译.
每当你收到消息时:
 Segmentation fault (core dumped)
核心文件写入当前目录.你可以用命令检查它
 gdb your_program core_file
该文件包含程序崩溃时的内存状态.在部署软件期间,核心转储可能很有用.
确保您的系统未将核心转储文件大小设置为零.您可以将其设置为无限制:
ulimit -c unlimited
虽然小心!核心转储可能会变得巨大.
asy*_*nts 11
有许多工具可以帮助调试分段错误,我想将我最喜欢的工具添加到列表中:Address Sanitizers(通常缩写为 ASAN)。
现代¹编译器带有方便的-fsanitize=address标志,增加了一些编译时间和运行时间开销,可以进行更多的错误检查。
根据文档,这些检查包括默认捕获分段错误。这里的优点是您可以获得类似于 gdb 输出的堆栈跟踪,但无需在调试器中运行程序。一个例子:
int main() {
  volatile int *ptr = (int*)0;
  *ptr = 0;
}
$ gcc -g -fsanitize=address main.c
$ ./a.out
AddressSanitizer:DEADLYSIGNAL
=================================================================
==4848==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x5654348db1a0 bp 0x7ffc05e39240 sp 0x7ffc05e39230 T0)
==4848==The signal is caused by a WRITE memory access.
==4848==Hint: address points to the zero page.
    #0 0x5654348db19f in main /tmp/tmp.s3gwjqb8zT/main.c:3
    #1 0x7f0e5a052b6a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26b6a)
    #2 0x5654348db099 in _start (/tmp/tmp.s3gwjqb8zT/a.out+0x1099)
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /tmp/tmp.s3gwjqb8zT/main.c:3 in main
==4848==ABORTING
输出比 gdb 的输出稍微复杂一些,但也有好处:
无需重现问题即可接收堆栈跟踪。只需在开发过程中启用标志就足够了。
ASAN 捕获的不仅仅是分段错误。即使进程可以访问该内存区域,也会捕获许多越界访问。
¹ 那是Clang 3.1+和GCC 4.8+。
以上所有答案均正确且推荐;如果无法使用上述方法,则此答案仅作为最后的手段。
如果所有其他方法都失败了,您始终可以使用各种临时调试打印语句(例如fprintf(stderr, "CHECKPOINT REACHED @  %s:%i\n", __FILE__, __LINE__);)重新编译您的程序,这些语句散布在您认为是代码的相关部分中。然后运行该程序,并观察崩溃发生之前打印的最后一个调试打印内容 - 您知道您的程序已经走到了这一步,因此崩溃一定是在该点之后发生的。添加或删除调试打印、重新编译并再次运行测试,直到将范围缩小到一行代码。此时,您可以修复错误并删除所有临时调试打印。
这很乏味,但它的优点是几乎可以在任何地方工作 - 唯一可能不会的情况是,如果您由于某种原因无法访问 stdout 或 stderr,或者您试图修复的错误是一个竞争-当程序时间改变时其行为发生变化的条件(因为调试打印会减慢程序速度并改变其时间)