2010-07-30 14 views
9

Ho un codice che si sta bloccando in un sistema di grandi dimensioni. Tuttavia, il codice si riduce essenzialmente al seguente pseudo-codice. Ho rimosso gran parte dei dettagli, poiché ho cercato di ridurlo alle ossa nude; Non penso che per questo manchi qualcosa di cruciale.Perché il vettore che elimina il distruttore viene chiamato come risultato di un'eliminazione scalare?

// in a DLL: 

#ifdef _DLL 
#define DLLEXP __declspec(dllexport) 
#else 
#define DLLEXP __declspec(dllimport) 
#endif 

class DLLEXP MyClass // base class; virtual 
{ 
public: 
    MyClass() {}; 
    virtual ~MyClass() {}; 

    some_method() = 0; // pure virtual 

    // no member data 
}; 

class DLLEXP MyClassImp : public MyClass 
{ 
public: 
    MyClassImp(some_parameters) 
    { 
    // some assignments... 
    } 

    virtual ~MyClassImp() {}; 

private: 
    // some member data... 
}; 

e:

// in the EXE: 

MyClassImp* myObj = new MyClassImp (some_arguments); // scalar new 
// ... and literally next (as part of my cutting-down)... 
delete myObj; // scalar delete 

Nota che corrispondono scalari scalare new e delete vengono utilizzati.

In un build di debug in Visual Studio (2008 Pro), in < dbgheap.c di Microsoft>, la seguente asserzione fallisce:

_ASSERTE(_CrtIsValidHeapPointer(pUserData)); 

Nella parte superiore dello stack sono i seguenti elementi:

mydll_d.dll!operator delete() 
mydll_d.dll!MyClassImp::`vector deleting destructor'() 

penso che questo dovrebbe essere

mydll_d.dll!MyClassImp::`scalar deleting destructor'() 

Cioè, il programma si comporta come se avessi scritto

MyClassImp* myObj = new MyClassImp (some_arguments); 
delete[] newObj; // array delete 

L'indirizzo in pUserData è quello di myObj stesso (al contrario di un membro). La memoria per quell'indirizzo assomiglia a questo:

       ... FD FD FD FD 
(address here) 
VV VV VV VV MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM 
FD FD FD FD AB AB AB AB AB AB AB AB EE FE EE FE 
... 

dove i quattro VV s sono presumibilmente l'indirizzo della tabella di funzione virtuale, il MM...MM è dati utente riconoscibile, e gli altri byte sono diversi marcatori speciali PUT in posizione dal debugger (ad esempio, gli FD FD s sono "byte di guardia" attorno all'archivio dell'oggetto).

Poco prima dell'asserzione, vedo la modifica VV s, e mi chiedo se ciò è dovuto a un passaggio alla tabella delle funzioni virtuali della classe base.

Sono a conoscenza del problema del livello sbagliato nella gerarchia di classi in fase di distruzione. Questo non è il problema qui; i miei distruttori sono tutti virtuali.

Rilevo pagina "BUG: operatore errato Elimina Chiamato per la Classe esportato" di Microsoft http://support.microsoft.com/kb/122675 ma che sembra essere per quanto riguarda l'eseguibile sbagliato (con il mucchio sbagliato) il tentativo di assumersi la responsabilità per la distruzione dei dati.

Nel mio caso, sembra che sia stato applicato il "sapore" errato di eliminazione del distruttore: , vale a dire vettoriale anziché scalare.

Sono in procinto di provare a produrre codice di riduzione minimo che presenta ancora il problema.

Tuttavia, qualsiasi suggerimento o suggerimento su come approfondire questo problema sarebbe molto apprezzato.

Forse il più grande indizio qui è lo mydll_d.dll!operator delete() in pila. Devo aspettarmi che questo sia myexe_d.exe!operator delete(), indicando che gli DLLEXP s sono stati "persi"?

Suppongo che questa potrebbe essere un'istanza di una doppia eliminazione (ma non la penso così).

C'è un buon riferimento che posso leggere per quanto riguarda gli assegni _CrtIsValidHeapPointer?

+1

Nel MyClassImp, il ctor e dtor sono probabilmente chiamati MyClassImp e non MyClass, non è vero? – Calvin1602

+1

Oh, e puoi F11 nel nuovo()? Qual é ? – Calvin1602

+0

@ Calvin1602 [1] grazie, sì, l'ho corretto ora. [2] il ten che viene chiamato è quello di 'MyClassImp' dopo/durante il quale viene chiamato quello di' MyClass'. – Rhubbarb

risposta

6

Sembra che questo potrebbe essere un problema di allocazione di un heap e tentativo di eliminare su un altro. Questo può essere un problema quando si assegnano oggetti da una DLL poiché la DLL ha il proprio heap. Dal codice che stai mostrando non sembra che questo sarebbe il problema ma forse nella semplificazione qualcosa è stato perso? In passato ho visto il codice come questo utilizzare le funzioni di fabbrica e i metodi virtuali destroy sugli oggetti per assicurarsi che l'allocazione e la cancellazione avvengano nel codice dll.

+1

+1: odio e detesto Windows per avermi fatto fare questo. –

+0

Questa era certamente la chiave per la risposta, così come il commento di Hans sulla domanda (per la quale, vedi anche la mia "risposta"). Come succede, il codice completo (piuttosto che il mio codice di cut-down) ha effettivamente metodi destroy(), così come le factory di creazione. Nonostante ciò, altri errori nelle impostazioni di compilazione hanno portato ancora ai problemi descritti. – Rhubbarb

1

Microsoft fornisce l'origine per il runtime C; puoi controllare lì per vedere cosa fa _CrtIsValidHeapPointer. Sulla mia installazione, è sotto C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\crt\src\dbgheap.c.

Un altro suggerimento è quello di verificare il disassemblaggio del

delete newObj; // scalar delete 

e confrontarlo con lo smontaggio generato per

delete[] newObj; 

e

delete pointerToClassLikeMyClassThatIsInExeAndNotDll; 

per testare la tua teoria su delete[] essere chiamato. Allo stesso modo, si potrebbe verificare lo stack di chiamate per

delete pointerToClassLikeMyClassThatIsInExeAndNotDll; 

per testare la sua teoria su mydll_d.dll!operator delete() contro myexe_d.exe!operator delete().

1

Grazie per tutte le risposte e i commenti. Tutti sono stati utili e pertinenti.

Qualsiasi altra informazione è ancora benvenuta.


Il seguente è stato un commento sulla mia domanda da Hans Passant:

volta che si avvia l'esportazione di classi da DLL, compilazione con/MD diventa molto importante. Sembra/MT per me.

Come risultato, ho dato un'occhiata più da vicino all'impostazione del collegamento in tutto il progetto. Ho trovato un'istanza 'sepolta' di/MT e/MTd che avrebbe dovuto essere/MD e/MDd, più alcune incoerenze correlate in altre impostazioni.

Dopo aver corretto questi, non viene generata alcuna asserzione e il codice sembra comportarsi correttamente.


Ecco alcune delle cose da controllare quando si verificano crash o errori di asserzione in esecuzione lascia scopi e distruttori sono chiamati. mantenere durante tutti i progetti (incluse le dipendenze) e in tutte le configurazioni (soprattutto in quello problematico):

(. Qui i percorsi * .vcproj sono relative al </VisualStudioProject/Configurazioni/Configurazione />)

  • Il runtime corretto è selezionato in C/C++ | Generazione del codice | Libreria di runtime < Tool [@ Name = "VCCLCompilerTool"]/@ RuntimeLibrary>;
  • Le definizioni appropriate (se presenti) sono apportate in C/C++ | Preprocessore | Definizioni preprocessore < strumento [@ Name = "VCCLCompilerTool"]/@ PreprocessorDefinitions> in particolare in materia di uso di statica contro librerie dinamiche (ad esempio _STLP_USE_STATIC_LIB contro _STLP_USE_DYNAMIC_LIB per STLport);
  • Le versioni appropriate delle librerie sono selezionate in Linker | Input | Dipendenze aggiuntive < strumento [@ Name = "VCLinkerTool"]/@ AdditionalDependencies> particolare in materia di librerie di runtime statiche contro 'wrapper' per DLL (ad esempio stlport_static.lib contro STLport. N.m lib).

interessante notare che il scalare 'sapore' di cancellare mi aspetto ancora non sembra essere essere chiamato (il punto di interruzione non è mai colpito). Cioè, vedo ancora il vettore che elimina il distruttore. Pertanto, potrebbe essere stata una "falsa pista".

Forse questo è solo un problema di implementazione Microsoft, o forse c'è ancora qualche sottigliezza che ho perso.

1

Questo comportamento è speciale per MSVC 9, in cui l'operatore di cancellazione per una classe esportata, che ha un distruttore virtuale viene generato e manipolato in modo implicito in vector dtor con un flag associato, dove 1 significa (scalare) e 3 significa (vettore).

Il vero problema con questa cosa è che interrompe la forma canonica di new/delete, in cui il codificatore del client non è in grado di disabilitare l'operatore delete del vettore nel suo codice, se pensa, che è una cattiva idea per usarlo.

Inoltre il vettore dtor, sembra anche essere eseguito in modo errato, se il nuovo è allocato in un altro modulo rispetto al modulo in cui risiede l'implementazione e viene quindi trattenuto in una variabile statica tramite un conteggio di riferimento, che esegue un delete this (qui entra in gioco il vettore dtor) allo spegnimento del processo.

Questo corrisponde al problema dell'heap "bshield" già menzionato prima, il dtor viene eseguito sull'heap sbagliato e il codice crahses con "non può leggere quella posizione di memoria" o "violazione di accesso" all'arresto - tali problemi sembrano essere molto comune

L'unico modo per aggirare questo bug, è vietare l'uso di un distruttore virtuale ed eseguirlo da sé, forzando l'uso di una funzione delete_questa dalla classe base - stupido come si imita la roba del virtual dtor dovrebbe fare per te Quindi viene eseguito anche lo scalar dtor e, in caso di arresto, qualsiasi oggetto conteggio ref condiviso tra i moduli, può essere istanziato in modo sicuro, poiché l'heap viene sempre indirizzato correttamente al modulo di origine.

Per verificare, se si verificano tali problemi, è sufficiente vietare l'utilizzo dell'operatore di eliminazione del vettore.

0

Nel mio caso, è che il 'sapore' sbagliato di eliminazione distruttore sembra essere applicata: vale a dire vettore piuttosto che scalare.

Questo non è il problema qui. Come per lo pseudocodice in Mismatching scalar and vector new and delete, lo scalar deleting destructor chiama semplicemente allo vector deleting descructor con una bandiera che dice "Do scalar destruction piuttosto che vector destruction".

Il tuo problema reale, come notato da altri poster, è che stai allocando su un heap e cancellando su un altro. La soluzione più chiara è quella di dare le vostre classi sovraccarichi di operator new e operator delete, come ho descritto in una risposta ad una domanda simile: Error deleting std::vector in a DLL using the PIMPL idiom

Problemi correlati