C ++ деструктор беспорядок, невозможно отладить - PullRequest
4 голосов
/ 04 декабря 2009

когда я запускаю свою программу, все идет хорошо. В конце он распечатывает это:

*** glibc detected *** ./streamShare: double free or corruption (fasttop): 0x08292130 ***
======= Backtrace: =========
/lib/tls/i686/cmov/libc.so.6[0xcc2ff1]
/lib/tls/i686/cmov/libc.so.6[0xcc46f2]
/lib/tls/i686/cmov/libc.so.6(cfree+0x6d)[0xcc779d]
/usr/lib/libstdc++.so.6(_ZdlPv+0x21)[0x1c86f1]
./streamShare[0x804be7f]
./streamShare[0x804be3e]
./streamShare[0x804abc0]
./streamShare[0x804a5f2]
./streamShare[0x804a1c4]
./streamShare[0x804a1d7]
./streamShare[0x804a46a]
./streamShare[0x804ba45]
./streamShare[0x804b49c]
./streamShare[0x804ac68]
./streamShare[0x804ac48]
./streamShare[0x804a676]
./streamShare[0x804a237]
./streamShare[0x8049a3f]
./streamShare[0x804d2e5]
./streamShare[0x804d34d]
/lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe6)[0xc6eb56]
./streamShare[0x8049361]

Я проверил, это происходит, когда функция возвращается, когда все объекты программы устанавливаются автоматически. Во всяком случае, я не определил никакой деструктор для этих объектов, я пытался использовать контейнеры STL и TR1 shared_ptr. Я думаю, что все происходит в деструкторах по умолчанию. Есть ли способ узнать, где он распадается? Я имею в виду, я хотел бы знать, какое разрушение объекта делает весь беспорядок. Я использую эти контейнеры и общие указатели:

typedef std::tr1::shared_ptr<messageListener> mlsptr;

typedef std::map<const char*, mlsptr, ltstr> CONSTCHP2MSLST;

messageListener не имеет деструктора. И два из этих векторов:

std::vector<MSG> queueto1;

где деструктор MSG:

MSG::~MSG() {
    destroy();
}

void MSG::destroy() {
    if (payload != NULL)
        delete[] payload;
    payload = NULL;
    payloadLen = 0;
}

, который никогда не доставлял проблем раньше, и я этого не должен ...

Любые рекомендации, как отследить эту проблему? Я невежественен ...

EDIT:

ЗДЕСЬ ВАЛГРИНД ВЫХОД:

valgrind ./streamShare -v
==25795== Memcheck, a memory error detector
==25795== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==25795== Using Valgrind-3.5.0-Debian and LibVEX; rerun with -h for copyright info
==25795== Command: ./streamShare -v
==25795== 
==25795== Invalid free() / delete / delete[]
==25795==    at 0x402454D: operator delete(void*) (vg_replace_malloc.c:346)
==25795==    by 0x804BCC0: std::tr1::_Sp_deleter<streamShare::messageListener>::operator()(streamShare::messageListener*) const (shared_ptr.h:97)
==25795==    by 0x804BC7F: std::tr1::_Sp_counted_base_impl<streamShare::messageListener*, std::tr1::_Sp_deleter<streamShare::messageListener>, (__gnu_cxx::_Lock_policy)2>::_M_dispose() (shared_ptr.h:75)
==25795==    by 0x804AAF7: std::tr1::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() (boost_sp_counted_base.h:140)
==25795==    by 0x804A58D: std::tr1::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count() (shared_ptr.h:153)
==25795==    by 0x804A173: std::tr1::__shared_ptr<streamShare::messageListener, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr() (shared_ptr.h:358)
==25795==    by 0x804A186: std::tr1::shared_ptr<streamShare::messageListener>::~shared_ptr() (shared_ptr.h:834)
==25795==    by 0x804A405: std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> >::~pair() (stl_pair.h:68)
==25795==    by 0x804D3D0: __gnu_cxx::new_allocator<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > >::destroy(std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> >*) (new_allocator.h:115)
==25795==    by 0x804D337: std::_Rb_tree<char const*, std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> >, std::_Select1st<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > >, streamShare::ltstr, std::allocator<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > > >::_M_destroy_node(std::_Rb_tree_node<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > >*) (stl_tree.h:383)
==25795==    by 0x804D29B: std::_Rb_tree<char const*, std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> >, std::_Select1st<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > >, streamShare::ltstr, std::allocator<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > > >::_M_erase(std::_Rb_tree_node<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > >*) (stl_tree.h:972)
==25795==    by 0x804D27B: std::_Rb_tree<char const*, std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> >, std::_Select1st<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > >, streamShare::ltstr, std::allocator<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > > >::_M_erase(std::_Rb_tree_node<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > >*) (stl_tree.h:970)
==25795==  Address 0x42c3358 is 0 bytes inside a block of size 8 free'd
==25795==    at 0x402454D: operator delete(void*) (vg_replace_malloc.c:346)
==25795==    by 0x804BCC0: std::tr1::_Sp_deleter<streamShare::messageListener>::operator()(streamShare::messageListener*) const (shared_ptr.h:97)
==25795==    by 0x804BC7F: std::tr1::_Sp_counted_base_impl<streamShare::messageListener*, std::tr1::_Sp_deleter<streamShare::messageListener>, (__gnu_cxx::_Lock_policy)2>::_M_dispose() (shared_ptr.h:75)
==25795==    by 0x804AAF7: std::tr1::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() (boost_sp_counted_base.h:140)
==25795==    by 0x804A58D: std::tr1::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count() (shared_ptr.h:153)
==25795==    by 0x804A173: std::tr1::__shared_ptr<streamShare::messageListener, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr() (shared_ptr.h:358)
==25795==    by 0x804A186: std::tr1::shared_ptr<streamShare::messageListener>::~shared_ptr() (shared_ptr.h:834)
==25795==    by 0x804A405: std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> >::~pair() (stl_pair.h:68)
==25795==    by 0x804D3D0: __gnu_cxx::new_allocator<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > >::destroy(std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> >*) (new_allocator.h:115)
==25795==    by 0x804D337: std::_Rb_tree<char const*, std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> >, std::_Select1st<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > >, streamShare::ltstr, std::allocator<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > > >::_M_destroy_node(std::_Rb_tree_node<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > >*) (stl_tree.h:383)
==25795==    by 0x804D29B: std::_Rb_tree<char const*, std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> >, std::_Select1st<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > >, streamShare::ltstr, std::allocator<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > > >::_M_erase(std::_Rb_tree_node<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > >*) (stl_tree.h:972)
==25795==    by 0x804D27B: std::_Rb_tree<char const*, std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> >, std::_Select1st<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > >, streamShare::ltstr, std::allocator<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > > >::_M_erase(std::_Rb_tree_node<std::pair<char const* const, std::tr1::shared_ptr<streamShare::messageListener> > >*) (stl_tree.h:970)
==25795== 
==25795== 
==25795== HEAP SUMMARY:
==25795==     in use at exit: 0 bytes in 0 blocks
==25795==   total heap usage: 22 allocs, 30 frees, 496 bytes allocated
==25795== 
==25795== All heap blocks were freed -- no leaks are possible
==25795== 
==25795== For counts of detected and suppressed errors, rerun with: -v
==25795== ERROR SUMMARY: 8 errors from 1 contexts (suppressed: 19 from 8)

Ответы [ 7 ]

27 голосов
/ 04 декабря 2009

Судя по вашему выводу Valgrind, проблема в том, что объект, на который указывает shared_ptr, удаляется дважды. Одна возможность получить это, если вы инициализируете два shared_ptr с одним и тем же необработанным указателем, например ::

int* p = new int(123);
shared_ptr<int> sp1(p);
shared_ptr<int> sp2(p);

shared_ptr не является магическим, и он не может знать, принадлежит ли объект, о котором вы просите, чтобы о нем позаботились, другим не связанным shared_ptr, если все, что вы даете, это необработанный указатель. В приведенном выше примере каждый shared_ptr создаст свой собственный счетчик ссылок, инициализированный 1; когда они умирают, каждый уменьшает свой счетчик, видит, что он равен 0, и удаляет объект, производя двойное удаление. Я подозреваю, что ваш случай похож. Если вы показываете код, используемый для инициализации shared_ptr объектов, которые добавляются в вектор, это можно проверить.

[РЕДАКТИРОВАТЬ] Так как это подтверждено, чтобы быть причиной сбоя сейчас, позвольте мне разработать немного о том, как правильно использовать shared_ptr.

Прежде всего, природа проблемы. Способ shared_ptr написан, он работает с любым типом C ++ и обеспечивает семантику подсчета ссылок. Очевидная проблема состоит в том, что большинство типов не предоставляют места для хранения счетчика ссылок (например, рассмотрим shared_ptr<int> - нет дополнительного пространства "внутри" int). Чтобы обойти это, для каждого общего объекта выделяется отдельный блок памяти, который содержит счетчик ссылок. Это делается всякий раз, когда вы создаете shared_ptr из необработанного указателя. Затем сам объект shared_ptr хранит исходный необработанный указатель и указатель на счетчик ссылок (именно поэтому он более «толстый», чем необработанный указатель, который можно тривиально проверить с помощью sizeof). Когда вы создаете один shared_ptr из другого (используя конструктор копирования или оператор присваивания), он копирует указатель на счетчик ссылок, поэтому все экземпляры shared_ptr, созданные друг от друга, поддерживают один счетчик и гарантируют правильное удаление. Но если у вас есть два несвязанных «семейства» shared_ptr объектов для одних и тех же объектов (где два или более указателей были созданы из одного и того же необработанного указателя), эти «семейства» не будут знать друг о друге и будут пересчитываться отдельно, и каждый удалит, когда он достигнет 0.

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

В std::tr1::shared_ptr или более старых версиях Boost единственный полностью безопасный шаблон для выделения объектов - это:

shared_ptr<T> x(new T(...));

Другими словами, результат new должен быть немедленно помещен в shared_ptr - вы можете скопировать последнее столько раз, сколько захотите.

Достаточно безопасный шаблон также такой:

auto_ptr<T> x(new T);
...
shared_ptr<T> y(x);

shared_ptr реализует обычную передачу права собственности при инициализации с auto_ptr, а семантика последнего (при условии, что они правильно соблюдаются) обеспечивает существование только одного auto_ptr для объекта; таким образом, можно сконструировать shared_ptr из этого.

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

В C ++ 0x std::shared_ptr и в более новых версиях boost::shared_ptr есть помощник , предоставленный для обеспечения правильной реализации общих объектов:

shared_ptr<int> p = make_shared<int>(123);

Тип возвращаемого значения make_shared<T>() уже равен shared_ptr<T>, поэтому ни в коем случае вы не имеете дело с необработанными указателями в своем коде, что снижает вероятность ошибиться.

2 голосов
/ 04 декабря 2009

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

Там не так много, но есть вероятность, что ваш ~MSG() будет вызван, когда вектор перераспределяется для роста.

Вот самая простая версия вашего кода:

#include <vector>

struct MSG {
  MSG(int count);
  ~MSG();
  void destroy();
  char* payload;
  int payloadLen;
};

MSG::MSG(int count): payload(new char[count]), payloadLen(count) {}

MSG::~MSG() {
    destroy();
}

void MSG::destroy() {
    if (payload != NULL)
        delete[] payload;
    payload = NULL;
    payloadLen = 0;
}

std::vector<MSG> queueto1;

int main() {
    queueto1.push_back(MSG(10));
    return 0;
}

А G ++ говорит :

блок освобожден дважды

Выход: ExitFailure 127

Теперь, когда вызывается push_back(MSG(10)), он создает временный экземпляр MSG и затем использует конструктор копирования по умолчанию , чтобы установить элемент в векторе, перед вызовом ~MSG() в временный (который удаляет полезную нагрузку, на которую теперь указывает элемент в векторе).

Вы можете реализовать конструктор копирования от MSG(const MSG& copy) до передачи владения, но это требует const_cast для копии и очень грязно.

Доступны две простые опции : MSG::payload - общий указатель, или queueto1 - std::vector<MSG*>

1 голос
/ 04 декабря 2009

для отслеживания выделения памяти и проблем с повреждением вы можете использовать valgrind или Rational Purify

0 голосов
/ 05 февраля 2013

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

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

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

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

0 голосов
/ 04 декабря 2009

Побочный вопрос: вы сказали, что не определяете деструкторы, а используете контейнеры STL и общие указатели. То, что контейнеры и общие указатели делают для вас, называется вашими деструкторами. Они не заменяют деструктора; они заменяют необходимость выяснить, когда использовать деструктор.

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

Если у вас есть class A и class B: public A, и у вас когда-либо есть указатель на A, указывающий на B (необработанный указатель, умный указатель, это не имеет значения), вам нужен виртуальный деструктор. В противном случае, когда указатель на A будет удален, он уничтожит все подклассы и члены A и проигнорирует все, что добавлено в B. Следовательно, у вас должен быть виртуальный деструктор в любом базовом классе, который будет использоваться таким образом, и ради безопасности у вас должен быть виртуальный деструктор в любом базовом классе с любой виртуальной функцией. Даже если в деструкторе не от чего избавляться, в вашем определении A.

должно быть virtual A::~A() {}.
0 голосов
/ 04 декабря 2009

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

0 голосов
/ 04 декабря 2009

Распечатайте указатель "payLoad" и счетчик по мере удаления. Затем вы можете увидеть двойное свободное событие, которое удаляет это ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...