Почему я получаю сегфолты случайно? - PullRequest
3 голосов
/ 21 мая 2009

Это довольно странно для меня, но я получаю неожиданную и случайную ошибку сегментации при запуске моей программы. Иногда это работает, иногда - происходит сбой. Отладчик Dev-C ++ указывает мне на строку файла: stl_construct.h

/**
   * @if maint
   * Constructs an object in existing memory by invoking an allocated
   * object's constructor with an initializer.
   * @endif
   */
  template<typename _T1, typename _T2>
    inline void
    _Construct(_T1* __p, const _T2& __value)
    {
      // _GLIBCXX_RESOLVE_LIB_DEFECTS
      // 402. wrong new expression in [some_]allocator::construct
     -> ::new(static_cast<void*>(__p)) _T1(__value);
    }

Кстати, я широко использую STL. Что мне делать, чтобы определить происхождение сегфо? Есть ли инструменты, которые могут помочь? Каковы причины, которые могут привести к случайным сбоям, подобным этому.

Edit:

Моя программа насчитывает около 5000 строк кода. Я не знаю, какой фрагмент кода мне нужно показать, чтобы получить некоторую помощь, поскольку я понятия не имею о происхождении проблемы, все, что я получил от отладчика, - это то, что он имеет отношение к STL.

Edit:

Я перешел на Code::Blocks сейчас, вот стек вызовов:

#0 00464635 std::_Construct<std::pair<double const, int>, std::pair<double const, int> >(__p=0xb543e8, __value=@0x10) (C:/Program Files/CodeBlocks/MinGW/bin/../lib/gcc/mingw32/3.4.5/../../../../include/c++/3.4.5/bits/stl_construct.h:81)
#1 00462306 std::_Rb_tree<double, std::pair<double const, int>, std::_Select1st<std::pair<double const, int> >, std::less<double>, std::allocator<std::pair<double const, int> > >::_M_create_node(this=0x406fe50, __x=@0x10) (C:/Program Files/CodeBlocks/MinGW/bin/../lib/gcc/mingw32/3.4.5/../../../../include/c++/3.4.5/bits/stl_tree.h:367)
#2 00461DA7 std::_Rb_tree<double, std::pair<double const, int>, std::_Select1st<std::pair<double const, int> >, std::less<double>, std::allocator<std::pair<double const, int> > >::_M_clone_node(this=0x406fe50, __x=0x0) (C:/Program Files/CodeBlocks/MinGW/bin/../lib/gcc/mingw32/3.4.5/../../../../include/c++/3.4.5/bits/stl_tree.h:379)
#3 004625C6 std::_Rb_tree<double, std::pair<double const, int>, std::_Select1st<std::pair<double const, int> >, std::less<double>, std::allocator<std::pair<double const, int> > >::_M_copy(this=0x406fe50, __x=0x0, __p=0x406fe54) (C:/Program Files/CodeBlocks/MinGW/bin/../lib/gcc/mingw32/3.4.5/../../../../include/c++/3.4.5/bits/stl_tree.h:1029)
#4 00462A9D std::_Rb_tree<double, std::pair<double const, int>, std::_Select1st<std::pair<double const, int> >, std::less<double>, std::allocator<std::pair<double const, int> > >::_Rb_tree(this=0x406fe50, __x=@0xb59a7c) (C:/Program Files/CodeBlocks/MinGW/bin/../lib/gcc/mingw32/3.4.5/../../../../include/c++/3.4.5/bits/stl_tree.h:559)
#5 0045A928 std::map<double, int, std::less<double>, std::allocator<std::pair<double const, int> > >::map(this=0x406fe50, __x=@0xb59a7c) (C:/Program Files/CodeBlocks/MinGW/bin/../lib/gcc/mingw32/3.4.5/../../../../include/c++/3.4.5/bits/stl_map.h:166)
#6 0040B7E2 VehicleManager::get_vehicles_distances(this=0xb59a50) (C:/Program Files/CodeBlocks/MinGW/projects/AHS/VehicleManager.cpp:232)
#7 00407BDA Supervisor::IsMergeInstruction(id_vehicle=1) (C:/Program Files/CodeBlocks/MinGW/projects/AHS/Supervisor.cpp:77)
#8 00408430 CheckingInstructionsThread(arg=0x476100) (C:/Program Files/CodeBlocks/MinGW/projects/AHS/Supervisor.cpp:264)
#9 00413950 _glfwNewThread@4() (??:??)
#10 75A24911    KERNEL32!AcquireSRWLockExclusive() (C:\Windows\system32\kernel32.dll:??)
#11 00476100    std::__ioinit() (??:??)
#12 0406FFD4    ??() (??:??)
#13 76E5E4B6    ntdll!RtlInitializeNtUserPfn() (C:\Windows\system32\ntdll.dll:??)
#14 00476100    std::__ioinit() (??:??)
#15 70266582    ??() (??:??)
#16 00000000    ??() (??:??)

Еще несколько точностью:

1 / Это многопоточное приложение. 2 / Метод: get_vehicles_distances (); возвращает карту. 3 / Возможно, карта не инициализируется к тому времени, когда она вызывается IsMergeInstruction ();

Edit:

Видимо, строка, вызывающая ошибку segault:

vehicles_distances_.erase(vehicles_distances_.begin(), vehicles_distances_.end());

Где vehicle_distances_ - Карта. Эта строка является частью метода: VehicleManager :: MoveAllVehicles ();

void VehicleManager::MoveAllVehicles() {

     vehicles_distances_.erase(vehicles_distances_.begin(), vehicles_distances_.end());

     vector<Vehicle>::iterator iter_end = VehicleManager::vehicles_.end();
     for(vector<Vehicle>::iterator iter = VehicleManager::vehicles_.begin();
     iter != iter_end; ++iter) {

          (*iter).MoveVehicle();

          vehicles_distances_[(*iter).get_vec_vehicle_position().y] = (*iter).get_id_vehicle();

     }

}

Что с этим не так?

Edit:

Я пытался использовать map :: clear (); в качестве замены для сопоставления :: erase (); но такая же проблема возникает!

Edit:

Я думаю, я понял ... Поток пытается использовать Vehicles_distances_, пока он очищен .. (?)

Edit:

Проблема решена! Так оно и было с карты :: erase (); как и ожидалось. я обошел проблему, создав другую переменную карты, где пара <key, value> была инвертирована, чтобы я мог обновить карту. (поскольку ключ, который мне нужен, это расстояние, а расстояние не уникально, так как оно меняется каждый раз, но id_vehicle уникален!). В конце я просто взял эту карту, снова инвертировал <key, value> и перенес ее на исходную карту, которую можно повторно объявить в каждом цикле ...

Спасибо всем!

Ответы [ 13 ]

10 голосов
/ 21 мая 2009

Во-первых, ради любви ко всему, что приятно, не используйте Dev-C ++ . Хотел бы я знать, как люди продолжают сталкиваться с этим мусором. Он не поддерживался в течение лет , и даже когда его поддерживали, он по-прежнему представлял собой гадкий кусок хлама, в котором отсутствовала базовая функциональность. Откажитесь от этого и отправляйтесь на одну из бесчисленных лучших бесплатных альтернатив.

Теперь, на ваш вопрос: ваша программа случайно выходит из строя, потому что вы сделали что-то незаконное ранее. Не делай этого. ;)

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

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

Так что да, эти ошибки сложно найти. Лучший совет, который я могу дать: 1) разбрызгивать утверждения по всему вашему коду, чтобы обеспечить поддержку базовых инвариантов, и 2) проходить через программу, и на каждом шагу проверять, что вы не читаете или не пишете по адресам, которые не принадлежит тебе.

В MSVC по умолчанию включена безопасная опция SCL, которая будет выполнять проверку границ кода STL, что может помочь в выявлении таких ошибок, как эта.

Я считаю, что в GCC есть возможность сделать что-то подобное (но по умолчанию оно не включено).

Сегфоуты противны. После того, как люди несколько раз были укушены подобной ошибкой, они становятся более дисциплинированными, избегая доступа к памяти из-за пределов. :)

6 голосов
/ 21 мая 2009

Очевидный вопрос будет "что такое _p". В отладчике вы сможете посмотреть на стек вызовов. Следуйте _p назад к его происхождению. Убедитесь, что указан правильный размер, что он не был удален и что он действительно существует.

Если это нелегко, всегда есть методы грубой силы для закомментирования случайного (подозреваемого) кода до тех пор, пока он не сработает или не вернется назад и не разойдется по известной рабочей копии.

6 голосов
/ 21 мая 2009

Вы можете попробовать Valgrind как способ помочь найти проблему. Учитывая строку, которую вы задали в вопросе, я должен был предположить, что вы либо повредили кучу, либо у вас проблема со стеком.

2 голосов
/ 22 мая 2009

Ваше описание и стек вызовов указывают на то, что во время инициализации статических переменных происходит сбой программы. Это распространенная ошибка C ++: не существует простого способа управления порядком инициализации для статических переменных.

Например, ваша программа может иметь объект foo, который зависит от объекта bar; но может случиться так, что программа вызывает конструктор для foo, прежде чем она создаст bar. Это было бы плохо и могло вызвать проблему, которую вы описываете. (Является ли CheckingInstructionsThread статической переменной, которая порождает поток? Это может быть проблемой прямо сейчас.)

Чтобы исправить это, вам может понадобиться просмотреть файлы .cpp вашей программы на предмет статических переменных (в том числе статических и глобальных), особенно тех, которые относятся к некоторому типу класса. Это может или не может помочь изменить ваши конструкторы, чтобы записать некоторые следы в stderr; используйте fprintf вместо cerr, если вы это сделаете.

РЕДАКТИРОВАТЬ : Если вы не уверены, имеет ли это какое-либо отношение к статике, попробуйте поставить такую ​​строку в начале main():

 fprintf(stderr, "main() entered\n");

Это не исключает статическую инициализацию как причину проблемы; даже если это не сбой до main(), вы все равно могли бы неправильно настроить структуры данных. Но если вы никогда не доберетесь до fprintf, то вы знаете , что причиной является статическая инициализация.

2 голосов
/ 21 мая 2009

Проблема, скорее всего, в вашем коде, чем в stl_construct.h. Я предполагаю, что этот файл является частью дистрибутива STL для Dev-C ++. Ошибка сегментации может происходить в коде, который был создан с использованием шаблонов в stl_construct.h, но основная причина проблемы будет в другом месте. Я бы попытался решить эту проблему, получив трассировку стека во время сбоя. Для каждой функции в трассировке стека (особенно недавно написанных) попробуйте изучить код и найти следующие типы возможных ошибок:

  • Неинициализированные переменные (особенно индексы, используемые для доступа к массиву)
  • Память, использованная после освобождения или удаления.
  • Доступ к массиву за пределами массива
  • Распределение памяти, которое используется без проверки на NULL (не проблема, если вы используете new, потому что это не возвращает NULL, оно выдает исключение)
2 голосов
/ 21 мая 2009

Вы можете использовать _CrtSetDbgFlag() в начале вашей программы, чтобы включить некоторые параметры отладки кучи. См. Также Куча отладки CRT . Это может помочь вам отследить, где вы делаете плохие вещи с памятью. Он доступен во время выполнения Microsoft C, с которым компилятор MinGW ссылается по умолчанию. Если вместо этого вы используете среду выполнения GNU C, вы не сможете идти по этому пути.

2 голосов
/ 21 мая 2009

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

1 голос
/ 22 мая 2009

Карты не очень дружелюбны к темам. Если вы выполняете операции с картой в коде потока, вам действительно нужно заблокировать все обращения к этой карте и понять, что любые итераторы, которые вы можете удерживать, могут быть признаны недействительными.

1 голос
/ 22 мая 2009

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

В стеке вызовов наиболее важным местом для поиска является ваш код (вызывающий) и параметры, которые вы передали функции, которую вы вызвали (вызываемый).

Без этой информации мы не сможем вам больше помочь. ; -)

Подробнее об ошибках сегментации: http://en.wikipedia.org/wiki/Segmentation_fault
(Необходимо прочитать, см. Также ссылки в разделах «См. Также» и «Внешние ссылки»)

1 голос
/ 22 мая 2009

Это похоже на опасную функцию:)

Одна вещь поразила меня, хотя. Куда уходит выделенная память? Интуитивно я хотел бы иметь указатель на указатель в качестве первого аргумента, а затем разыменовать его. Как это:

template<typename _T1, typename _T2>
    inline void
    _Construct(_T1** __p, const _T2& __value)
    {

       ::new(static_cast<void*>(*__p)) _T1(__value);
    }

В качестве альтернативы ссылка на указатель:

template<typename _T1, typename _T2>
    inline void
    _Construct(_T1*& __p, const _T2& __value)
    {

       ::new(static_cast<void*>(__p)) _T1(__value);
    }
...