Domanda

Si scopre che molti innocentemente guardando le cose sono un comportamento indefinito in C++.Per esempio, una volta un non-null puntatore è stato delete'd anche la stampa che il valore del puntatore è un comportamento indefinito.

Ora le perdite di memoria sono sicuramente male.Ma che classe sono definiti, indefiniti o di quello che la classe di comportamento?

È stato utile?

Soluzione

perdite di memoria.

Non v'è alcun comportamento indefinito. E 'perfettamente legale per una perdita di memoria.

comportamento indefinito: è lo standard azioni specificamente non vuole definire e lascia fino l'implementazione in modo che sia flessibile per eseguire determinati tipi di ottimizzazioni senza rompere lo standard

.

La gestione della memoria è ben definito.
Se si alloca dinamicamente la memoria e non la si rilascia. Poi il ricordo resta di proprietà della applicazione per gestire come meglio ritiene opportuno. Il fatto che hai perso tutti i riferimenti a quella parte di memoria non è né qui né là.

Naturalmente se si continua a perdere, allora vi troverete infine a corto di memoria disponibile e l'applicazione inizierà a generare eccezioni bad_alloc. Ma questo è un altro problema.

Altri suggerimenti

Le perdite di memoria sono sicuramente definiti in C / C ++.

Se faccio:

int *a = new int[10];

seguito da

a = new int[10]; 

Sono sicuramente che perde la memoria in quanto non v'è alcun modo per accedere alla prima assegnato array e questa memoria non viene liberato automaticamente come non è supportata GC.

Ma le conseguenze di questa perdita sono imprevedibili e variano da applicazione ad applicazione e da macchina a macchina per una stessa data applicazione. Di 'un'applicazione che si blocca a causa di perdite su una macchina potrebbe funzionare bene su un altro computer con più RAM. Anche per una data applicazione su una determinata macchina l'incidente a causa di perdite può apparire in momenti diversi durante la corsa.

Se si perdita di memoria, l'esecuzione procede come se non succede nulla. Questo è definito il comportamento.

In fondo alla pista, possono trova che una chiamata a malloc fallisce a causa di lì non essere abbastanza memoria disponibile. Ma questo è un comportamento definito di malloc, e le conseguenze sono anche ben definiti:. Il malloc chiamata restituisce NULL

Ora, questo può causare un programma che non controlla il risultato di malloc a fallire con una violazione di segmentazione. Ma questo comportamento non definito è (dal POV delle specifiche linguistiche) a causa del programma di dereferenziazione un puntatore non valido, non è la perdita di memoria prima o la chiamata malloc fallito.

La mia interpretazione di questa affermazione:

  

Per un oggetto di un tipo di classe con un distruttore non banale, la   programma non è necessario chiamare il distruttore esplicitamente prima della   memorizzazione che l'oggetto occupa vengono riutilizzati o rilasciato; tuttavia, se   non v'è alcuna chiamata esplicita al distruttore o se un delete-espressione   (5.3.5) non viene utilizzato per rilasciare l'archiviazione, il distruttore non deve   essere implicitamente chiamato e qualsiasi programma che dipende dagli effetti collaterali   prodotta dal distruttore è undefined comportamento.

è la seguente:

Se si riesce in qualche modo a liberare il di archiviazione che l'oggetto occupa senza di chiamare il distruttore per l'oggetto che ha occupato la memoria, UB è la conseguenza, se il distruttore è non banale e ha effetti collaterali.

Se new assegna con malloc, la storage raw potrebbe essere rilasciato con free(), il distruttore non correre, e UB comporterebbe. Oppure, se un puntatore è gettato a un tipo non correlato e cancellato, la memoria viene liberata, ma le piste destructor sbagliate, UB.

Questa non è la stessa di un delete omessa, dove la memoria sottostante non viene liberata. Tralasciando delete non è UB.

(commento qui sotto "Heads-up: questa risposta è stata spostata qui da non una perdita di memoria causa un comportamento indefinito " -?. probabilmente dovrete leggere questa domanda per ottenere una corretta sfondo per questa risposta O_o)

Mi sembra che questa parte del Principio consente esplicitamente:

  • avere un pool di memoria personalizzate che gli oggetti posizionamento new in, quindi rilasciare / riutilizzare il tutto senza spendere il tempo di chiamare i loro distruttori, finché non dipendono lato- effetti dei distruttori oggetto .

  • librerie che allocano un po 'di memoria e mai rilasciarla, probabilmente perché le loro funzioni / oggetti possano essere utilizzati da distruttori di oggetti statici e registrati gestori in-uscita, e non vale la pena di acquistare in tutta orchestrata-ordine -di-distruzione o "Phoenix" transitoria -come rinascita ogni volta quei accessi avvengono.

Non riesco a capire perché Standard sceglie di lasciare il comportamento indefinito quando ci sono dipendenze sugli effetti collaterali - invece di dire semplicemente questi effetti collaterali non avranno accaduto e lasciare che il programma hanno definito o comportamento non definito come ci si aspetterebbe normalmente dato questa premessa.

possono ancora considerare cosa , dice lo Standard è un comportamento indefinito. La parte cruciale è:

  

"dipende sugli effetti collaterali prodotti dal distruttore è undefined comportamento".

Lo Standard §1.9 / 12 definisce esplicitamente effetti collaterali come segue (il corsivo qui sotto sono gli standard, indicando l'introduzione di una definizione formale):

  

Accesso a un oggetto designato da un glvalue volatile (3.10), modifica di un oggetto, chiamare una libreria di funzioni di I / O, o chiamando una funzione che fa una di queste operazioni sono tutti effetti collaterali , che sono cambiamenti nello stato dell'ambiente di esecuzione.

Nel programma, non c'è alcuna dipendenza in modo che nessun comportamento non definito.

Un esempio di dipendenza probabilmente corrispondenza lo scenario in §3.8 p4, dove il bisogno di o causa di un comportamento indefinito non è evidente, è il seguente:

struct X
{
    ~X() { std::cout << "bye!\n"; }
};

int main()
{
     new X();
}

Una questione persone stanno discutendo è se l'oggetto X sopra sarebbe considerato released ai fini del 3,8 p4, dato probabilmente è liberato soltanto al O.S. dopo la fine del programma - non è chiaro dalla lettura del standard se tale fase di "vita" di un processo è di portata per i requisiti comportamentali del standard (la mia ricerca rapida dello Standard non ha chiarito questo). Io personalmente pericolo che 3.8p4 applica qui, in parte perché fino a quando è abbastanza ambigua per essere sostenuto uno scrittore compilatore può sentono in diritto per consentire un comportamento indefinito in questo scenario, ma anche se il codice di cui sopra non costituisce rilasciare lo scenario di facile ala modificata ...

int main()
{
     X* p = new X();
     *(char*)p = 'x';   // token memory reuse...
}

In ogni caso, tuttavia principale di attuare il distruttore sopra ha una effetto collaterale - per "chiamare una libreria di funzionalità I / O"; inoltre, il comportamento osservabile del programma probabilmente "dipende" nel senso che i buffer che sarebbero interessati dalla distruttore erano per aver eseguito sono lavata durante la terminazione. Ma è "dipende dagli effetti collaterali" solo ha significato per alludere a situazioni in cui il programma avrebbe potuto chiaramente undefined comportamento se il distruttore non ha funzionato? Mi piacerebbe sbagliare sul lato della prima, in particolare per quanto quest'ultimo caso non avrebbe bisogno di un paragrafo dedicato nel standard per documentare che il comportamento è indefinito. Ecco un esempio con un comportamento ovviamente-non definito:

int* p_;

struct X
{
    ~X() { if (b_) p_ = 0; else delete p_; }
    bool b_;
};

X x{true};

int main()
{
     p_ = new int();
     delete p_; // p_ now holds freed pointer
     new (&x){false};  // reuse x without calling destructor
}

Quando distruttore di x viene chiamato durante la terminazione, b_ saranno false e ~X() saranno quindi delete p_ per un puntatore già liberata, creando comportamento indefinito. Se x.~X(); era stato chiamato prima del riutilizzo, p_ sarebbe stato impostato su 0 e la cancellazione sarebbe stato al sicuro. In questo senso, il comportamento corretto del programma potrebbe dire dipendere il distruttore, e il comportamento è chiaramente definito, ma hanno abbiamo appena realizzato un programma che corrisponde 3.8p4 di descritti comportamenti a sé stante, piuttosto che avere il comportamento sia una conseguenza di 3.8p4 ...?

scenari più sofisticati con problemi - troppo lungo per fornire codice - potrebbero includere ad esempio una libreria strano C ++ con contatori di riferimento all'interno oggetti stream di file che ha dovuto colpire 0 per innescare alcune elaborazioni, come vampate di calore di I / O o unione di thread in background, ecc - in cui il fallimento di fare quelle cose che non rischiava solo in mancanza di eseguire uscita esplicitamente richiesto dal il distruttore, ma anche non riuscendo a uscita altra uscita tamponata dal flusso, o su qualche OS con un file system transazionale potrebbe comportare un rollback di precedenti di I / o -. tali questioni potrebbe cambiare il comportamento del programma osservabili o addirittura lasciare il programma appeso

Nota: non è necessario dimostrare che non c'è alcun codice vero e proprio che si comporta stranamente su qualsiasi compilatore / sistema esistente; lo Standard si riserva chiaramente il destro per i compilatori di avere un comportamento indefinito ... questo è tutto quello che conta. Questo non è qualcosa che si può ragionare e scegliere di ignorare la Standard - può essere che C ++ 14 o qualche altro revisione modifica questa clausola, ma finché è lì se poi c'è anche probabilmente un po 'di "dipendenza", a effetti collaterali poi c'è il rischio di comportamento non definito (che, ovviamente, è a sua volta permesso di essere definito da un particolare compilatore / implementazione, in modo che non significa automaticamente che ogni compilatore è obbligato fare qualcosa di bizzarro).

La specifica del linguaggio non dice nulla a proposito di "perdite di memoria".Dalla lingua punto di vista, quando si crea un oggetto in memoria dinamica, è proprio quello che stanno facendo:la creazione di un oggetto anonimo, con durata illimitata/durata di conservazione."Illimitato" in questo caso significa che l'oggetto può solo terminare il suo ciclo di vita/durata di archiviazione quando è esplicitamente deallocarlo, ma per il resto si continua a vivere per sempre (fino a quando il programma viene eseguito).

Ora, consideriamo di solito un oggetto allocato dinamicamente diventare una "perdita di memoria", al punto di esecuzione del programma, quando tutti i riferimenti (generico "riferimenti", come i puntatori) a quello oggetto sono perso al punto di essere irreversibile.Nota, che anche per un uomo, il concetto di "tutti i riferimenti perso" non è molto preciso e definito.Che cosa succede se abbiamo un riferimento a una parte dell'oggetto, che può essere teoricamente "ricalcolato" in riferimento all'intero oggetto?È una perdita di memoria o no?Che cosa succede se non abbiamo riferimenti all'oggetto qualunque, ma in qualche modo siamo in grado di calcolare un punto di riferimento, utilizzando alcune delle altre informazioni a disposizione per il programma (come precisa sequenza di assegnazioni)?

La specifica del linguaggio non preoccuparsi di questioni del genere.Qualunque cosa tu consideri un aspetto di "perdita di memoria" nel programma, dalla lingua punto di vista è un non-evento a tutti.Dalla lingua punto di vista di un "fuoriuscito" allocato dinamicamente l'oggetto continua a vivere felicemente fino a quando il programma termina.Questo è l'unico punto di interesse:cosa succede quando il programma termina e alcune di memoria dinamica è ancora allocato?

Se non ricordo male, la lingua non specifica che cosa succede alla dinamica della memoria che è ancora allocato il momento di chiusura del programma.Nessun tentativo verrà fatto automaticamente distruggere/deallocare gli oggetti creati in memoria dinamica.Ma non c'è alcuna formale un comportamento indefinito in casi come questo.

L'onere della prova è a coloro che pensano una perdita di memoria potrebbe essere C ++ UB.

Naturalmente nessuna prova è stata presentata.

In breve per chiunque ospitare qualsiasi dubbio a questa domanda non può mai essere chiaramente risolto, se non da molto credibile minaccia il comitato per esempio con musica ad alto volume Justin Bieber, in modo che essi aggiungono una dichiarazione C ++ 14 che chiarisce che non è UB.


In discussione è C ++ 11 §3.8 / 4:

  

Per un oggetto di un tipo di classe con un distruttore non banale, il programma non è necessario per chiamare il distruttore esplicitamente prima della memorizzazione che l'oggetto occupa vengono riutilizzati o rilasciato; tuttavia, se non v'è alcuna chiamata esplicita al distruttore o se un eliminare l'espressione (5.3.5) non viene utilizzato per rilasciare l'archiviazione, il distruttore non deve essere implicitamente chiamato e qualsiasi programma che dipende gli effetti collaterali prodotti dal distruttore è undefined comportamento.

Questo passaggio ha avuto la stessa identica formulazione C ++ 98 e C ++ 03. Che cosa significa?

  • il programma non è tenuto a chiamare il distruttore esplicitamente prima dello stoccaggio, che l'oggetto occupa viene riutilizzato o rilasciato

    -. Significa che si può afferrare la memoria di una variabile e riutilizzare quella memoria, senza prima distruggere l'oggetto esistente

  • se non v'è alcuna chiamata esplicita al distruttore o se un delete-espressione (5.3.5) non viene utilizzato per rilasciare l'archiviazione, il distruttore non deve essere chiamato implicitamente

    - significa che se uno non distrugge l'oggetto esistente prima del riutilizzo memoria, allora se l'oggetto è tale che il suo distruttore viene chiamato automaticamente (ad esempio una variabile automatica locale) Comportamento allora il programma è indefinito, perché distruttore opereranno su un no oggetto più esistente.

  • comportamento e qualsiasi programma che dipende dagli effetti collaterali prodotti dal distruttore ha undefined

    - non può significare letteralmente quello che dice, perché un programma dipende sempre effetti collaterali, dalla definizione di effetto collaterale. O in altre parole, non c'è alcun modo per il programma di non dipendere dagli effetti collaterali, perché allora non sarebbero gli effetti collaterali.

Molto probabilmente ciò che si intendeva non era quello che finalmente fatto la sua strada in C ++ 98, in modo che ciò che abbiamo a portata di mano è un difetto .

Dal contesto si può intuire che se un programma si basa sulla distruzione automatica di un oggetto di tipo noto staticamente T, in cui la memoria è stato riutilizzato per creare un oggetto o gli oggetti che non è un oggetto T, allora questo è un comportamento indefinito .


Coloro che hanno seguito il commento può notare che la spiegazione sopra della parola “deve” non è il significato che ho assunto in precedenza. Per come la vedo ora, il “deve” non è un requisito in merito all'attuazione, quello che è permesso fare. Si tratta di un requisito del programma, ciò che è permesso il codice di fare.

Quindi, questo è formalmente UB:

auto main() -> int
{
    string s( 666, '#' );
    new( &s ) string( 42, '-' );    //  <- Storage reuse.
    cout << s << endl;
    //  <- Formal UB, because original destructor implicitly invoked.
}

Ma questo è OK con un'interpretazione letterale:

auto main() -> int
{
    string s( 666, '#' );
    s.~string();
    new( &s ) string( 42, '-' );    //  <- Storage reuse.
    cout << s << endl;
    //  OK, because of the explicit destruction of the original object.
}

Un problema principale è che con un'interpretazione letterale del comma della norma sopra di esso sarebbe ancora formalmente OK se la nuova collocazione ha creato un oggetto di tipo diverso lì, proprio a causa della distruzione esplicita dell'originale. Ma non sarebbe molto in pratica OK in questo caso. Forse questo è coperto da qualche altro punto nella norma, in modo che sia anche formalmente UB.

E questo è anche OK, utilizzando il new posizionamento dal <new>:

auto main() -> int
{
    char* storage   = new char[sizeof( string )];
    new( storage ) string( 666, '#' );
    string const& s = *(
        new( storage ) string( 42, '-' )    //  <- Storage reuse.
        );
    cout << s << endl;
    //  OK, because no implicit call of original object's destructor.
}

Come la vedo io - ora

.

La sua sicuramente definiti il comportamento.

Si consideri un caso in cui il server è in esecuzione e mantenere l'allocazione della memoria heap e nessuna memoria si sblocca, anche se non c'è uso di esso. Da qui il risultato finale sarebbe tale server alla fine sarà a corto di memoria e sicuramente si verificherà crash.

Aggiunta a tutte le altre risposte, alcune approccio completamente diverso.Guardando allocazione di memoria in § 5.3.4-18 siamo in grado di vedere:

Se una qualsiasi parte di inizializzazione dell'oggetto sopra descritto76 termina lanciando un'eccezione e un'adeguata funzione di deallocazione può essere trovato, la funzione di deallocazione è chiamato a liberare la memoria in cui l'oggetto era in fase di costruzione, dopo che l'eccezione continua per propagare nel contesto della nuova espressione.Se non univoca corrispondente funzione di deallocazione può essere trovato, propagazione dell'eccezione non causa la memoria dell'oggetto per essere liberato.[ Nota:Questo è appropriato, quando la detta funzione di ripartizione di non allocare memoria;in caso contrario, è probabile che a causare una perdita di memoria.—fine nota ]

Sarebbe causa UB qui, si sarebbe detto, così è "solo una perdita di memoria".

In luoghi come §20.6.4-10, un possibile garbage collector e rilevatore di perdite è menzionato.Un sacco di pensiero è stato messo in il concetto di sicurezza di derivati puntatori et.al.per essere in grado di utilizzare il C++ con un garbage collector (C. 2.10 "supporto Minimo per la garbage collection regioni").

Così se sarebbe UB di perdere l'ultimo puntatore a un oggetto, lo sforzo avrebbe alcun senso.

Per quanto riguarda il "quando il distruttore ha effetti collaterali, non si esegue mai UB" direi che questo è sbagliato, altrimenti strutture come std::quick_exit() sarebbe intrinsecamente UB troppo.

Se la navetta spaziale deve decollare in due minuti, e ho una possibilità di scegliere tra mettere in su con il codice che perdite di memoria e il codice che ha un comportamento indefinito, sto mettendo nel codice che perdite di memoria.

Ma la maggior parte di noi non sono di solito in una situazione del genere, e se siamo, è probabilmente da un guasto più in alto della linea. Forse mi sbaglio, ma mi sto leggendo questa domanda: "Quale peccato mi farà entrare in un inferno più veloce?"

Probabilmente il comportamento non definito, ma in realtà sia.

definito, dal momento che una perdita di memoria è che si dimentica di pulire dopo voi stessi.

Naturalmente, una perdita di memoria probabilmente può causare un comportamento non definito in seguito.

risposta dritto in avanti: La norma non definisce cosa succede quando si perdita di memoria, quindi è "indefinito". E 'implicitamente definito, però, che è meno interessante rispetto alle cose in modo esplicito non definiti nello standard.

Questo comportamento, ovviamente, non può essere indefinito. Semplicemente perché UB deve accadere ad un certo punto nel tempo, e dimenticando di liberare memoria o chiamare un distruttore non accadere in qualsiasi punto nel tempo. Ciò che accade è proprio questo il programma termina senza dover mai rilasciato memoria o chiama il distruttore; questo non rende il comportamento del programma o della sua terminazione, non definito in alcun modo.

Detto questo, a mio parere la norma stessa si contraddice in questo passaggio. Da un lato si assicura che il distruttore non sarà chiamato in questo scenario, e d'altra parte si dice che se il programma dipende dagli effetti collaterali prodotti dal distruttore comportamento allora è indefinito. Supponiamo che il exit chiamate distruttore, allora nessun programma che non tutto può fingere di essere indipendente da quella, in quanto l'effetto collaterale di chiamare il distruttore le impedirebbe di fare quello che altrimenti fare; ma il testo assicura anche che il distruttore non verrà chiamato in modo che il programma può andare avanti con il fare la sua roba indisturbato. Credo che l'unico modo ragionevole di leggere alla fine di questo passaggio è che se il corretto comportamento del programma sarebbe richiedono il distruttore di essere chiamato, quindi il comportamento è in realtà non è definita; questo allora è un'osservazione superflua, dato che è appena stato stabilito che il distruttore non verrà chiamato.

comportamento indefinito significa, che avverrà non è stato definito o è sconosciuto. Il comportamento di perdite di memoria è sicuramente noto in C / C ++ per erodere memoria disponibile. I problemi che derivano, tuttavia, non sempre possono essere definite e variano come descritto da GameOver.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top