Reputation: 1389
The code itself is extremely simple. I'm using Catch2 to unit test, (I really like its interface) and break into gdb
, but getting no useful information for a Seg. fault thrown by said simple code.
I know exactly what is causing the problem, but I don't know why, or how I would've gotten the offending line of code from gdb
(I've extensive usage with the Python equivalent, pdb
, but errors in Python seem to be a lot more straightforward).
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);
}
}
}
The main.cpp contains everything it should, alongside the downloaded catch.hpp (as instructed by the tutorial).
I compile this with: g++ Flop.cpp test_Flop.cpp main.cpp -o run_test
and run it with gdb -ex run --args ./run_test -b
, which allows Catch2 to break into the debugger. The result is this:
Program received signal SIGSEGV, Segmentation fault.
0x0000555555566e9e in ____C_A_T_C_H____T_E_S_T____0() ()
With backtrace:
#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 ()
Ok. So, SIGSEGV
indicates that we tried to read/write to memory that a process does not have access to. If, in Flop.hpp, I instead say int tiles_[10][10][10]
, then everything works fine. So setting tiles_
to a larger size is somehow reserving a piece of memory that cannot be accessed? I'm new to C++ (and thus new to actually thinking about what's going on in the computer when I program something) so correct me if I'm wrong, but int tiles_[200][200][200]
shouldn't take much more than 32MB of memory, right?
So with this, I have a couple of questions:
gdb
to get me to the offending line of code? The un-simplified version of this code is a few hundred lines total. Luckily, my problem was early on in the class's definition, but commenting everything out and (painstakingly) uncommenting line-by-line still took a while, and this is what gdb
was meant to prevent!Upvotes: 0
Views: 1023
Reputation: 22152
The size of the array
int tiles_[200][200][200];
is about 30 MB, assuming sizeof(int) == 4
.
This is larger than typical stack size limits, and so you will write outside the stack space that you are allowed to use when you create an automatic variable of this type with
Flop flop;
The amount of stack usable by a program is generally limited to a few MB or so, depending on the OS and settings.
gdb
gives you the location of the segmentation fault: The entry to the test function. Stack space for local variables in a function is allocated usually when the function is entered, so this is where a stack overflow may manifest in a segmentation fault (or depending on how exactly the stack is handled later, when the out-of-bounds stack space is written/read the first time).
Don't save large objects directly as members or with automatic storage duration (i.e. on the stack). Instead allocate large objects dynamically (i.e. on the free store / heap), through a pointer indirection.
The easiest way of doing this is to use std::vector
instead of built-in arrays. This is also generally preferable over built-in arrays and should be your default choice if you need to store multiple objects of the same type.
In the specific cases where compile-time sized non-allocating arrays are required, std::array
is superior to built-in arrays as well. In that way you can avoid ever using built-in arrays at all.
Alternatively std::unique_ptr<...>
allows you to wrap any object type (also built-in arrays) in a dynamically allocated indirection.
Upvotes: 4