Rilevamento di errori di utilizzo di delete [] vs. delete in fase di compilazione


19

Vorrei sapere se è possibile rilevare l' deleteerrore commentato di seguito in fase di compilazione? In particolare, mi piacerebbe conoscere il compilatore g ++.

ClassTypeA *abc_ptr = new ClassTypeA[100];  
abc_ptr[10].data_ = 1;  
delete abc_ptr; // error, should be delete []  

7
Non dovresti comunque chiamare elimina manualmente.
Martin York,

9
@LokiAstari Pensi davvero che quel commento sia stato utile?
James,

5
@James: Sì. La chiave è "manualmente".
Martin York,

A volte il rispetto di ciò implicherebbe la riscrittura di molti codici legacy
Nick Keighley il

Usa std::unique_ptr<ClassTypeA[]>e quindi non è necessario.
user253751

Risposte:


6

In generale, il compilatore non è in grado di rilevare tali errori. Esempio: supponiamo che il costruttore per alcune classi alloca alcuni membri dei dati usando new TypeName[], ma il distruttore usa erroneamente deleteinvece di delete[]. Se il costruttore e il distruttore sono definiti in unità di compilazione separate, come può sapere il compilatore durante la compilazione del file che definisce il distruttore che l'uso è incompatibile con quello nel file compilato separatamente che definisce il costruttore?

Per quanto riguarda i compilatori GNU, non lo è. Come notato sopra, non può farlo nel caso generale. Un compilatore non deve rilevare errori new / delete non corrispondenti perché si tratta di un comportamento indefinito. UB è la scheda "esci di prigione" del fornitore del compilatore.

Strumenti come valgrind sono in grado di rilevare questi tipi di disallineamenti nuovi / eliminati, ma lo fanno in fase di esecuzione. Potrebbe esserci uno strumento di analisi statica che esamina tutti i file di origine che verranno eventualmente compilati per formare un eseguibile, ma non ho alcuno di tali strumenti di analisi statica che rilevi questo tipo di errore.


Ho usato uno strumento di analisi statica chiamato Parasoft che ha sicuramente una regola per questo scenario specifico. Funziona su tutti i file in un particolare progetto (se è stato configurato correttamente). Detto questo, non sono sicuro di come gestisca scenari come il commento di Pete Kirkham sulla risposta di Kilian Foth.
Velociraptors

28

È possibile utilizzare le classi RAII appropriate per delete. Questo è l'unico modo sicuro per farlo, e questo errore è solo uno dei moltissimi che incontrerai nel chiamarti delete.

Utilizzare sempre le classi per gestire le risorse dinamiche della durata e il sistema dei tipi imporrà la corretta distruzione delle risorse.

Modifica: "Cosa succede se si sta verificando il codice e non è possibile modificarlo?" Sei fottuto.


18
-1 perché questo non risponde effettivamente alla domanda.
Mason Wheeler,

2
L'unico modo per rilevare la mancata corrispondenza è utilizzare il sistema dei tipi, che prevede l'utilizzo delle classi RAII.
DeadMG,

9
... ha ancora meno senso. Cosa c'entra l'uso delle classi RAII - un meccanismo di runtime - con le informazioni di sistema di tipo statico che il compilatore conosce al momento della compilazione?
Mason Wheeler,

6
@MasonWheeler vedi boost :: shared_ptr e boost :: shared_array come esempi. La distruzione di shared_ptr elimina l'oggetto, la distruzione di shared_array elimina [] s l'array. Non è possibile assegnare un shared_array a shared_ptr, quindi - purché non si costruisca un shared_ptr con un array in primo luogo - il sistema dei tipi impedisce l'utilizzo dell'eliminazione errata.
Pete Kirkham,

4
Normalmente, una risposta come questa è più odiosa che utile. Tuttavia, in questo caso, è effettivamente vero. Sta cercando l'applicazione da parte del compilatore di un errore comune e l'utilizzo di RAII impedisce correttamente questo stile di errore, dandogli così esattamente quello che vuole. +1
riwalk

10

Questo errore particolare: sì. Questo tipo di errore in generale: purtroppo no! Ciò implicherebbe la previsione del flusso di esecuzione senza eseguirlo effettivamente, e ciò non è possibile per i programmi arbitrari. (Ecco perché la maggior parte dei compilatori non prova nemmeno a rilevare casi semplici come il tuo esempio.)

Pertanto la risposta di DeadMG è quella appropriata: non cercare di farlo bene prestando attenzione: l'attenzione umana è fallibile. Utilizzare i mezzi forniti dalla lingua e prestare attenzione al computer.


In che modo è necessario prevedere il flusso di esecuzione? Mi sembra una conoscenza puramente statica in fase di compilazione; il sistema di tipi del compilatore sa cos'è un array e cosa no.
Mason Wheeler,

Anche in presenza di calchi? Scusa, se ho sbagliato, eliminerò la risposta.
Kilian Foth,

12
@MasonWheeler è il tipo statico di abc_ptr, ClassTypeA*quindi è possibile inserire una linea tra il nuovo e l'eliminazione. if ( rand() % 2 == 1 ) abc_ptr = new ClassTypeA;Nulla nel sistema di tipo statico mostra se abc_ptrpunta a un array o un oggetto dinamico o si divide parzialmente in un altro oggetto o array.
Pete Kirkham,

...Oh giusto. Sono così abituato a lavorare con linguaggi con tipi di array reali che continuo a dimenticare quanto è stato rovinato in C-land. :(
Mason Wheeler,

1
@Pete Kirkham, @Mason Wheeler: Tuttavia, il runtime dovrebbe vedere quanti oggetti sono memorizzati all'indirizzo indicato da abc_ptr, altrimenti come potrebbe essere in grado di distribuire la giusta quantità di memoria? Quindi il runtime sa quanti oggetti devono essere deallocati.
Giorgio,

4

Il caso banale che mostri può essere rilevato al momento della compilazione, poiché l'istanza e la distruzione dell'oggetto sono nello stesso ambito. In generale, la cancellazione non è nello stesso ambito, o anche nello stesso file di origine, dell'istanza. E un tipo di puntatore C ++ non contiene informazioni sul fatto che faccia riferimento a un singolo oggetto del suo tipo o a un array, per non parlare dello schema di allocazione. Quindi non è possibile diagnosticare questo in fase di compilazione in generale.

Perché non diagnosticare i casi speciali che sono possibili?

In C ++ esistono già strumenti per gestire la perdita di risorse dinamiche legate agli ambiti, vale a dire puntatori intelligenti e matrici di livello superiore ( std::vector).

Anche se usi il deletesapore corretto , il tuo codice non è ancora sicuro dell'eccezione. Se il codice tra new[]e delete[]termina con un'uscita dinamica, l'eliminazione non viene mai eseguita.

Per quanto riguarda il rilevamento in fase di esecuzione, lo Valgrindstrumento fa un buon lavoro nel rilevare ciò in fase di esecuzione. Orologio:

==26781== Command: ./a.out
==26781==
==26781== Mismatched free() / delete / delete []
==26781==    at 0x402ACFC: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==26781==    by 0x8048498: main (in /home/kaz/test/a.out)
==26781==  Address 0x4324028 is 0 bytes inside a block of size 80 alloc'd
==26781==    at 0x402B454: operator new[](unsigned int) (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==26781==    by 0x8048488: main (in /home/kaz/test/a.out)

Ovviamente, Valgrind non funziona su tutte le piattaforme e non è sempre pratico o possibile riprodurre tutte le situazioni di runtime sotto lo strumento.


dici che questo banale caso può essere rilevato al momento della compilazione. Potresti dirmi quale comando di compilazione usi per raggiungerlo?
SebGR,

"può essere rilevato al momento della compilazione" qui significa che è facile da implementare in un compilatore, non che g ++ lo abbia. Un compilatore ha a portata di mano l'intera durata dell'identificatore durante l'elaborazione di tale ambito e può propagare le informazioni di allocazione come attributo semantico legato alla sintassi.
Kaz,

-3

Alcuni banali esempi di rilevamento in fase di compilazione / analisi statica:

Su un host RHEL7 con cppcheck 1.77 and 1.49

> cat test.cc
#include <memory>
int main(){char* buf = new char[10];delete buf;}

http://cppcheck.sourceforge.net/

> cppcheck -x c++ test.cc
Checking test.cc ...
[test.cc:2]: (error) Mismatching allocation and deallocation: buf

Con clang++ 3.7.1su RHEL7

> clang++ --analyze -x c++ test.cc
test.cc:2:37: warning: Memory allocated by 'new[]' should be deallocated by
'delete[]', not 'delete'
int main(){char* buf = new char[10];delete buf;}
                                    ^~~~~~~~~~
1 warning generated.

L'analizzatore statico Clang può anche rilevare quando std::unique_ptrnon viene passato<char[]>

> cat test2.cc
#include <memory>
int main(){std::unique_ptr<char> buf(new char[10]);}

https://clang-analyzer.llvm.org/

> clang++ --analyze -x c++ -std=c++11 test2.cc
In file included from test2.cc:1:
In file included from /opt/rh/devtoolset-4/root/usr/lib/gcc/x86_64-redhat-linux/5.3.1/
../../../../include/c++/5.3.1/memory:81:
/opt/rh/devtoolset-4/root/usr/lib/gcc/x86_64-redhat-linux/5.3.1/
../../../../include/c++/5.3.1/bits/unique_ptr.h:76:2: 
warning: Memory allocated by
      'new[]' should be deallocated by 'delete[]', not 'delete'
        delete __ptr;
        ^~~~~~~~~~~~
1 warning generated.

Aggiorna di seguito con un link al lavoro che ha aggiunto questo a clang, i test e un bug che ho trovato.

Questo è stato aggiunto al clang con recensioni.llvm.org/D4661 - "Rileva gli usi 'new' e 'delete' non corrispondenti" .

I test sono in test / Analisi / Mancata corrispondenzaDeallocator-checker-test.mm

Ho trovato questo bug aperto - bugs.llvm.org/show_bug.cgi?id=24819


Nessuno dubita che sia possibile trovare un analizzatore statico che rilevi uno specifico utilizzo errato, invece uno che rilevi tutti gli usi sbagliati (e si spera che non ci siano usi corretti )
Caleth
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.