2013-03-15 17 views
8

Ieri mi sono imbattuto in miseria che mi ha portato 24 ore di frustrazione. Il problema si è ridotto agli arresti anomali imprevisti verificatisi su base casuale. Per complicare le cose, il debug dei report aveva assolutamente lo pattern casuale. Per complicarlo ancora di più, tutte le tracce di debug stavano portando a o alle DLL native di a caso, dimostrando ogni volta che il problema non è dalla mia parte.Pimpl + QSharedPointer - Destructor = Disaster

Eccovi alcuni esempi di tali relazioni belle:

Program received signal SIGSEGV, Segmentation fault. 
0x0000000077864324 in ntdll!RtlAppendStringToString() from C:\Windows\system32\ntdll.dll 
(gdb) bt 
#0 0x0000000077864324 in ntdll!RtlAppendStringToString() from C:\Windows\system32\ntdll.dll 
#1 0x000000002efc0230 in ??() 
#2 0x0000000002070005 in ??() 
#3 0x000000002efc0000 in ??() 
#4 0x000000007787969f in ntdll!RtlIsValidHandle() from C:\Windows\system32\ntdll.dll 
#5 0x0000000000000000 in ??() 

warning: HEAP: Free Heap block 307e5950 modified at 307e59c0 after it was freed 
Program received signal SIGTRAP, Trace/breakpoint trap. 
0x00000000778bf0b2 in ntdll!ExpInterlockedPopEntrySListFault16() from C:\Windows\system32\ntdll.dll 
(gdb) bt 
#0 0x00000000778bf0b2 in ntdll!ExpInterlockedPopEntrySListFault16() from C:\Windows\system32\ntdll.dll 
#1 0x000000007786fd34 in ntdll!RtlIsValidHandle() from C:\Windows\system32\ntdll.dll 
#2 0x0000000077910d20 in ntdll!RtlGetLastNtStatus() from C:\Windows\system32\ntdll.dll 
#3 0x00000000307e5950 in ??() 
#4 0x00000000307e59c0 in ??() 
#5 0x00000000ffffffff in ??() 
#6 0x0000000000220f10 in ??() 
#7 0x0000000077712d60 in WaitForMultipleObjectsEx() from C:\Windows\system32\kernel32.dll 
#8 0x0000000000000000 in ??() 

Program received signal SIGSEGV, Segmentation fault. 
0x0000000000a9678a in QBasicAtomicInt::ref (this=0x8) at ../../include/QtCore/../../../qt-src/src/corelib/arch/qatomic_x86_64.h:121 
121 : "memory"); 
(gdb) bt 
#0 0x0000000000a9678a in QBasicAtomicInt::ref (this=0x8) at ../../include/QtCore/../../../qt-src/src/corelib/arch/qatomic_x86_64.h:121 
#1 0x00000000009df08e in QVariant::QVariant (this=0x21e4d0, p=...) at d:/Distributions/qt-src/src/corelib/kernel/qvariant.cpp:1426 
#2 0x0000000000b4dde9 in QList<QVariant>::value (this=0x323bd480, i=1) at ../../include/QtCore/../../../qt-src/src/corelib/tools/qlist.h:666 
#3 0x00000000009ccff7 in QObject::property (this=0x3067e900, 
name=0xa9d042a <QCDEStyle::drawPrimitive(QStyle::PrimitiveElement, QStyleOption const*, QPainter*, QWidget const*) const::pts5+650> "_q_stylerect") 
at d:/Distributions/qt-src/src/corelib/kernel/qobject.cpp:3742 
#4 0x0000000000000000 in ??() 

Come si può vedere questa roba è piuttosto brutta, dà uno alcuna informazione utile . Ma c'era una cosa a cui non prestavo attenzione. E 'stato un avvertimento strano durante la compilazione che è anche difficile da catturare con un occhio:

In file included from d:/Libraries/x64/MinGW-w64/4.7.2/Qt/4.8.4/include/QtCore/qsharedpointer.h:50:0, 
       from d:/Libraries/x64/MinGW-w64/4.7.2/Qt/4.8.4/include/QtCore/QSharedPointer:1, 
       from ../../../../source/libraries/Project/sources/Method.hpp:4, 
       from ../../../../source/libraries/Project/sources/Slot.hpp:4, 
       from ../../../../source/libraries/Project/sources/Slot.cpp:1: 
d:/Libraries/x64/MinGW-w64/4.7.2/Qt/4.8.4/include/QtCore/qsharedpointer_impl.h: In instantiation of 'static void QtSharedPointer::ExternalRefCount<T>::deref(QtSharedPointer::ExternalRefCount<T>::Data*, T*) [with T = Project::Method::Private; QtSharedPointer::ExternalRefCount<T>::Data = QtSharedPointer::ExternalRefCountData]': 
d:/Libraries/x64/MinGW-w64/4.7.2/Qt/4.8.4/include/QtCore/qsharedpointer_impl.h:336:11: required from 'void QtSharedPointer::ExternalRefCount<T>::deref() [with T = Project::Method::Private]' 
d:/Libraries/x64/MinGW-w64/4.7.2/Qt/4.8.4/include/QtCore/qsharedpointer_impl.h:401:38: required from 'QtSharedPointer::ExternalRefCount<T>::~ExternalRefCount() [with T = Project::Method::Private]' 
d:/Libraries/x64/MinGW-w64/4.7.2/Qt/4.8.4/include/QtCore/qsharedpointer_impl.h:466:7: required from here 
d:/Libraries/x64/MinGW-w64/4.7.2/Qt/4.8.4/include/QtCore/qsharedpointer_impl.h:342:21: warning: possible problem detected in invocation of delete operator: [enabled by default] 
d:/Libraries/x64/MinGW-w64/4.7.2/Qt/4.8.4/include/QtCore/qsharedpointer_impl.h:337:28: warning: 'value' has incomplete type [enabled by default] 

In realtà, mi rivolsi a questo avviso solo come ultima risorsa, perché in un inseguimento ad esempio alla disperata ricerca di un bug, il codice è stato già infetto dal disboscamento letterale.

Dopo aver letto attentamente, ho ricordato che, per esempio, se si utilizza std::unique_ptr o std::scoped_ptr per Pimpl - si dovrebbe certamente fornire desctructor, altrimenti il ​​codice non sarà nemmeno la compilazione. Tuttavia, ricordo anche che lo std::shared_ptr non si preoccupa del distruttore e funziona senza di esso. Era un'altra ragione per cui non prestavo attenzione a questo strano avvertimento. Per farla breve, quando ho aggiunto il distruttore, questo arresto casuale si è interrotto. Sembra che il numero QSharedPointer di Qt abbia alcuni difetti di progettazione rispetto allo std::shared_ptr. Immagino sarebbe meglio, se gli sviluppatori di Qt trasformassero questo avviso nell' perché il debugging di maratone del genere non vale semplicemente il tempo, lo sforzo e i nervi .

Le mie domande sono:

  1. Cosa c'è di sbagliato con QSharedPointer? Perché il distruttore è così vitale?
  2. Perché l'arresto anomalo si è verificato in assenza di un distruttore? Questi oggetti (che utilizzano Pimpl + QSharedPointer) vengono creati sullo stack e nessun altro oggetto ha accesso ad essi dopo la loro morte. Tuttavia, si è verificato un arresto anomalo durante alcuni periodo di tempo casuale dopo la loro morte.
  3. Qualcuno si è imbattuto in problemi del genere prima? Per favore, condividi la tua esperienza.
  4. Ci sono altre insidie ​​ come quella in Qt - quelle che io devono sapere per sicuro di rimanere sicuro in futuro?

Speriamo che queste domande e il mio post in generale aiuteranno gli altri a evitare il diavolo in cui sono stato nelle ultime 24 ore.

+2

In generale, si consiglia di utilizzare il flag -Werror per trasformare tutti gli avvisi in errori. Richiede un po 'di lavoro per risolvere tutti gli avvertimenti, ma l'ho trovato prezioso per avvisarmi di potenziali problemi (sia che tu usi Qt o meno). – cgmb

+1

La differenza tra QSharedPtr e shard_ptr è in come e quando il codice che esegue la cancellazione di T * viene istanziato - e quel codice ha ovviamente bisogno di conoscere la dichiarazione di T per chiamare il dtor. La cura è prendere sul serio gli avvertimenti (come suggerito da Slavik81). –

risposta

3

il problema è stato aggirato in Qt 5, vedere https://codereview.qt-project.org/#change,26974

Il compilatore di chiamare il distruttore sbagliato o assumendo un layout di memoria diversa probabilmente portare a un qualche tipo di corruzione della memoria. Direi che un compilatore dovrebbe dare un errore per questo problema e non un avvertimento.

1

Ti imbatterai in un problema simile a std::unique_ptr, che può anche causare distruttori danneggiati se utilizzato con un tipo incompleto. La correzione è piuttosto banale, ovviamente - dichiaro un costruttore per la classe, allora definisco nel file di implementazione come

MyClass::~MyClass() = default; 

La ragione per cui questo è un problema per std::unique_ptr ma non std::shared_ptr è che il distruttore fa parte del tipo del primo, ma è un membro di quest'ultimo.