Почему это может вызвать ошибку Seg. ошибка, и как я могу использовать GDB для ее отладки?

Сам код чрезвычайно прост. Я использую 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);
        }
    }
}

main.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] не должен занимать больше 32 МБ памяти, верно?

В связи с этим у меня есть пара вопросов:

  • Почему это вызывает ошибку сегментации?
  • Как я могу использовать gdb, чтобы добраться до оскорбительной строки кода? Неупрощенная версия этого кода состоит всего из нескольких сотен строк. К счастью, моя проблема была в начале определения класса, но закомментировать все и (кропотливо) раскомментировать строку за строкой по-прежнему требовалось некоторое время, и это то, что gdb должно было предотвратить!

person AmagicalFishy    schedule 19.03.2020    source источник
comment
Отвечает ли это на ваш вопрос? Переполнение стека C++   -  person S.M.    schedule 19.03.2020
comment
ошибки в Python кажутся намного более простыми. Ошибки в python оформляются опытными программистами, пытающимися информировать пользователей (других программистов) о возникающих скрытых проблемах. Когда вы программируете на C++, вы часто являетесь программистом, предоставляющим эту полезную информацию, поэтому позаботьтесь о том, что произойдет. Люди полагаются на то, что вы сообщаете о хороших вещах.   -  person Ted Lyngmo    schedule 19.03.2020


Ответы (1)


Размер массива

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

составляет около 30 МБ, если предположить, что sizeof(int) == 4.

Это больше, чем типичные ограничения размера стека, и поэтому вы будете писать за пределами пространства стека, которое вам разрешено использовать при создании автоматической переменной этого типа с помощью

Flop flop;

Объем стека, который может использовать программа, обычно ограничен несколькими МБ или около того, в зависимости от ОС и настроек.

gdb дает вам местоположение ошибки сегментации: вход в тестовую функцию. Пространство стека для локальных переменных в функции обычно выделяется при входе в функцию, поэтому именно здесь переполнение стека может проявляться в ошибке сегментации (или в зависимости от того, как именно обрабатывается стек позже, когда стек выходит за пределы стека). пробел пишется/читается в первый раз).

Не сохраняйте большие объекты напрямую как члены или с автоматической продолжительностью хранения (т. е. в стеке). Вместо этого выделяйте большие объекты динамически (т. е. в свободном хранилище/куче) с помощью косвенного указателя.

Самый простой способ сделать это — использовать std::vector вместо встроенных массивов. Это также обычно предпочтительнее встроенных массивов и должно быть вашим выбором по умолчанию, если вам нужно хранить несколько объектов одного типа.

В конкретных случаях, когда требуются нераспределяющие массивы размера времени компиляции, std::array также превосходит встроенные массивы. Таким образом, вы можете вообще не использовать встроенные массивы.

В качестве альтернативы std::unique_ptr<...> позволяет вам обернуть любой тип объекта (также встроенные массивы) в динамически выделяемую косвенность.

person walnut    schedule 19.03.2020
comment
Ах, так как пространство стека для локальных переменных выделяется во время выполнения, gdb знает только то, что что-то выполнялось, что-то сделало, чтобы вызвать Seg. вина, и как только Seg. ошибка была вызвана, gdb сама по себе не имеет возможности углубиться в то, какая часть какой-либо связанной функции конкретно вызвала ее — верно? Если бы я содержал весь класс Flip в одном файле .cpp и использовал #include Flip.cpp в test_Flip.cpp, смог бы gdb сказать мне, где в Flip этот Seg. вина была вызвана? - person AmagicalFishy; 19.03.2020
comment
Да, когда программа выдает ошибку из-за доступа к памяти, которая ей не разрешена, лучшее, что может сделать gdb, — это показать вам инструкцию сборки, в которой это произошло (вы можете переключиться на компоновку сборки с помощью команды layout в gdb) и, возможно, строка исходного кода, если она может быть связана, в данном случае функция head. В исполняемом файле не осталось дополнительной информации, которая могла бы дать вам дополнительную информацию. Это принципиально отличается от, например. Python, но в то же время является причиной того, что код C++ обычно намного быстрее, чем код Python, потому что ему не нужно добавлять все это. - person walnut; 19.03.2020
comment
@AmagicalFishy Структура файла не играет роли, и вы никогда не должны включать файл .cpp в другой файл. Должны быть включены только заголовочные файлы. Лучшее, что вы можете сделать, чтобы получить наиболее подробную информацию, - это как можно больше разделить на отдельные функции, а затем не использовать параметры оптимизации (вы не использовали их в командной строке вашего примера компилятора). Затем трассировка стека, которую вы получите, покажет отдельные функции. Использование флагов оптимизации (таких как -O2) приведет к тому, что короткие функции будут встроены, что вернет указанное выше преимущество для отладки. - person walnut; 19.03.2020
comment
Вы также должны всегда компилировать с параметром -g, если вы отлаживаете свою программу. Это добавляет дополнительную отладочную информацию в исполняемый файл, так что gdb может дать вам более точную информацию. Для выпускных сборок вы никогда не должны использовать -g, но всегда должны включать оптимизацию. - person walnut; 19.03.2020
comment
Причина, по которой я спросил о #include Flip.ccp, не столько из-за файловой структуры, сколько потому, что мне было интересно, одинаков ли базовый код сборки. Честно говоря, когда мы #include, препроцессор в основном копирует и вставляет все, что находится в нашем включаемом файле, поэтому, если бы gdb мог сказать мне, где в test_Flip.cpp произошел разрыв, мог бы он указать, где именно это произошло во включенном< /i> Flip.cpp файл (поскольку он просто копируется/вставляется в test_flip.cpp)? Не волнуйтесь, я знаю, что не нужно включать исходный файл, это просто вопрос, рожденный из любопытства. - person AmagicalFishy; 19.03.2020