【问题标题】:Why would this cause a Seg. fault, and how can I use GDB to debug it?为什么这会导致Seg。故障,如何使用 GDB 调试它?
【发布时间】:2020-03-19 04:09:26
【问题描述】:

代码本身非常简单。我正在使用Catch2 进行单元测试,(我真的很喜欢它的界面)并闯入gdb,但没有获得关于Seg 的有用信息。所述简单代码引发的错误。

我确切地知道 是什么 导致了问题,但我不知道 为什么,或者我如何从 gdb 获得有问题的代码行(我已经广泛使用 Python 等效项 pdb,但 Python 中的错误似乎更直接)。

Flop.hpp

#ifndef FLOP
#define FLOP
class Flop {
     private:
        int tiles_[200][200][200];
     public:
          Flop();
}
#endif

Flop.cpp

#include "Flop.hpp"
Flop::Flop() { }

test_Flop.cpp

#include "catch.hpp"
#include "Flop.hpp"
SCENARIO("I bang my head against a wall") {
    Flop flop;
    WHEN("I try to run this test") {
        THEN("This program SEGFAULTs") {
            REQUIRE(1==1);
        }
    }
}

ma​​in.cpp 包含它应该包含的所有内容,以及下载的 catch.hpp(按照教程的说明)。

我用g++ Flop.cpp test_Flop.cpp main.cpp -o run_test 编译它并用gdb -ex run --args ./run_test -b 运行它,这允许Catch2 进入调试器。结果是这样的:

Program received signal SIGSEGV, Segmentation fault.
0x0000555555566e9e in ____C_A_T_C_H____T_E_S_T____0() ()

带回溯:

#0  0x0000555555566e9e in ____C_A_T_C_H____T_E_S_T____0() ()
#1  0x000055555557e15e in Catch::TestInvokerAsFunction::invoke() const ()
#2  0x000055555557d7b1 in Catch::TestCase::invoke() const ()
#3  0x0000555555577f0a in Catch::RunContext::invokeActiveTestCase() ()
#4  0x0000555555577c59 in Catch::RunContext::runCurrentTest(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&) ()
#5  0x000055555557671b in Catch::RunContext::runTest(Catch::TestCase const&) ()
#6  0x00005555555797cc in Catch::(anonymous namespace)::TestGroup::execute() ()
#7  0x000055555557ab49 in Catch::Session::runInternal() ()
#8  0x000055555557a853 in Catch::Session::run() ()
#9  0x00005555555b6195 in int Catch::Session::run<char>(int, char const* const*) ()
#10 0x000055555558fdf0 in main ()

好的。因此,SIGSEGV 表示我们试图读取/写入进程无权访问的内存。如果在 Flop.hpp 中我改为使用int tiles_[10][10][10],那么一切正常。那么将tiles_设置为更大的大小是在某种程度上保留了一块无法访问的内存吗?我是 C++ 的新手(因此当我编写某些东西时,我实际上是在思考计算机中发生的事情)所以如果我错了请纠正我,但 int tiles_[200][200][200] 不应该占用超过 32MB 的内存,对吧?

因此,我有几个问题:

  • 为什么会导致分段错误?
  • 如何使用gdb 让我进入有问题的代码行?这段代码的未简化版本总共有几百行。幸运的是,我的问题在课程定义的早期就出现了,但是将所有内容注释掉并(煞费苦心地)逐行取消注释仍然需要一段时间,而这正是 gdb 旨在防止的!

【问题讨论】:

  • 这能回答你的问题吗? Stack overflow C++
  • "Python 中的错误似乎更直接" - python 中的错误由熟练的程序员修饰,试图让用户(其他程序员)意识到遇到的潜在问题。当您编写 C++ 时,您通常是提供有用信息的程序员,因此请注意发生的情况。人们依靠你回报好东西。

标签: c++ segmentation-fault gdb catch2


【解决方案1】:

数组的大小

int tiles_[200][200][200];

大约是 30 MB,假设 sizeof(int) == 4

这大于典型的堆栈大小限制,因此您将在创建此类自动变量时允许使用的堆栈空间之外写入

Flop flop;

程序可用的堆栈量通常限制在几 MB 左右,具体取决于操作系统和设置。

gdb 为您提供分段错误的位置:测试函数的入口。函数中局部变量的堆栈空间通常在进入函数时分配,因此这是堆栈溢出可能表现为分段错误的地方(或者取决于稍后处理堆栈的方式,当越界堆栈时)空间是第一次写入/读取)。

不要将大型对象直接保存为成员或自动存储持续时间(即在堆栈上)。而是通过指针间接动态分配大对象(即在空闲存储/堆上)。

最简单的方法是使用std::vector 而不是内置数组。这通常也比内置数组更可取,如果您需要存储多个相同类型的对象,这应该是您的默认选择。

在需要编译时大小的非分配数组的特定情况下,std::array 也优于内置数组。这样你就可以完全避免使用内置数组。

另外,std::unique_ptr&lt;...&gt; 允许您将任何对象类型(也包括内置数组)包装在动态分配的间接地址中。

【讨论】:

  • 啊,所以,由于局部变量的堆栈空间是在运行时分配的,gdb 只知道正在运行的任何东西都会导致 Seg。故障,并且一旦那个赛格。造成了故障,gdb 本身并没有办法进一步深入了解某些链接函数的哪一部分具体导致了它——对吧?如果我将整个Flip 类包含在单个.cpp 文件中,并在test_Flip.cpp 中使用#include Flip.cpp,那么gdb 将能够告诉我在Flip 中那个段。是什么原因造成的?
  • 是的,当程序 sefgaults 因为它访问不允许的内存时,gdb 可以做的最好的事情是向您显示它发生的汇编指令(您可以使用layout gdb 中的命令),如果可以关联,也可能是源代码行,在这种情况下是函数头。可执行文件中没有其他信息可以为您提供更多信息。这与例如根本不同。 Python,但同时也是 C++ 代码通常比 Python 代码快得多的原因,因为它不需要添加所有这些东西。
  • @AmagicalFishy 文件结构不起作用,您应该永远在另一个文件中包含.cpp 文件。只应包括头文件。要获得最详细的信息,您可以做的最好的事情是尽可能多地分离成单独的函数,然后不使用优化选项(您在示例编译器命令行中没有使用)。然后,您将获得的堆栈跟踪将显示各个函数。使用优化标志(例如-O2)将导致内联短函数,从而恢复上述调试优势。
  • 如果您正在调试您的程序,您还应该始终使用-g 选项进行编译。这会在可执行文件中添加额外的调试信息,因此 gdb 可以为您提供更精确的信息。对于发布版本,您永远不应该使用-g,但始终启用优化。
  • 我问#include Flip.ccp 的原因不是因为文件结构,而是因为我想知道底层的汇编代码是否相同。 Afaik,当我们#include 时,预处理器基本上会复制并粘贴我们包含的文件中的任何内容——所以如果gdb 能够告诉我在test_Flip.cpp 中中断发生的位置,它可能会指向它发生的确切位置 在包含的 Flip.cpp 文件中(因为它只是复制/粘贴到test_flip.cpp 中)?不用担心,我知道不要包含源文件,这只是出于好奇而提出的问题。
猜你喜欢
  • 1970-01-01
  • 2015-05-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多