2013-01-11 3 views
8

ho alcuni problemi con distruttore, nel codice seguente:distruttore di un oggetto statico costruito all'interno il distruttore di un altro oggetto statico

#include <stdlib.h> 
#include <cstdio> 

class Foo2 
{ 
    public: 
     Foo2() { printf("foo2 const\n"); } 

     ~Foo2() 
     { 
      printf("foo2 dest\n"); // <--- wasn't called for bionic libc 
     } 
}; 

static Foo2& GetFoo2() 
{ 
    static Foo2 foo2; 
    printf ("return foo2\n"); 
    return foo2; 
} 

class Foo1 
{ 
    public: 
     Foo1() { printf("foo1 const\n"); } 

     ~Foo1() 
     { 
      printf("foo1 dest\n"); 
      GetFoo2(); 
     } 
}; 

int main(int argc, const char* argv[]) 
{ 
     printf("main 1 \n"); 
     static Foo1 anotherFoo; 
     printf("main 2 \n"); 
} 

Perché distruttore per foo2 non è stato chiamato per bionic ed è stato per glibc?

EDIT
Uscita per bionico:

main 1 
foo1 const 
main 2 
foo1 dest 
foo2 const 
return foo2 

Debug Info:

(gdb) break 22 
Breakpoint 1 at 0x8048858: file test.C, line 22. 
(gdb) info breakpoints 
Num  Type   Disp Enb Address What 
1  breakpoint  keep y 0x08048858 in Foo2::~Foo2() at test.C:22 
(gdb) cont 
[ exited with code 0] 
+3

dovrebbe mostrare un'uscita –

+0

cosa sono bionic e glibc? –

+3

@LuchianGrigore Due diffuse implementazioni di libreria standard C e C++: glibc è quello GNU, Bionic è per Android. –

risposta

6

Penso che il tuo codice abbia un comportamento indefinito, sebbene lo standard non sia molto chiaro (o non lo trovo nello standard ). Il codice costruisce un nuovo oggetto statico nel distruttore di un oggetto statico. La norma non affronta questo caso, ma:

  1. Essa dice che distruttori devono essere richiamati nel inverso ordine di costruzione. Nel tuo caso, ciò implicherebbe che l'oggetto statico in GetFoo2 deve essere destrutturato prima che fosse costruito, che è contraddittorio.

  2. Il testo in §3.6/3 descrive il sequenziamento dei distruttori e le funzioni registrate con atexit. I requisiti sono tali da dover utilizzare lo stesso meccanismo di registrazione per ciascuno. E chiamare atexit dopo aver chiamato exit (o restituito da main) è un comportamento non definito.

  3. C'è anche §3.6/2, che dice che "Se una funzione contiene un oggetto blocco-portata della durata statica o di stoccaggio filo che è stata distrutta e la funzione viene chiamata durante l' distruzione di un oggetto con memorizzazione statica o thread durata, il programma ha un comportamento indefinito se il flusso del controllo passa attraverso la definizione dell'oggetto oggetto blockscope distrutto in precedenza ". Questa frase parla di oggetti danneggiati danneggiati, ma non ci vuole molta immaginazione per pensare a che l'assenza di oggetti "non ancora costruiti" è solo una supervisione .

Alla fine, direi che il mio primo punto di cui sopra è conclusiva con per quanto riguarda l'intenzione. In §1.3.24, c'è una nota (non normativa, ma indicativa di intenti) "Si può prevedere un comportamento non definito quando questa norma internazionale omette qualsiasi de fi nizione esplicita del comportamento o quando un programma utilizza un costrutto errato o dati errati ". In questo caso, l'unica descrizione del comportamento richiesto è impossibile (dato che non è possibile distruggere un oggetto prima che sia stato creato) e lo standard non dice nulla su come questo dovrebbe essere risolto.

+0

Sono meno convinto.(1) Penso che l'intento sia come se tutte le statistiche fossero in pila, il che non rappresenta un problema per questo codice. (2) Non penso che i distruttori statici usino atexit. Penso che questo sia un comportamento ben definito. –

+0

Posso concordare l'indefinito basato su §1.3.24 ma questo è tutto. Non vedo nulla (in n3337) nel §3.6 [terminazione] che si rivolge al caso specifico in questione. Tutto ciò che viene affrontato è che se un oggetto viene costruito dopo l'altro, allora il suo distruttore deve essere chiamato per primo, ma normalmente costruiamo un oggetto durante l'esecuzione dei distruttori ... –

+0

Non vedo alcun comportamento indefinito o contraddizione. La durata di vita di Foo1 è terminata (da quando è iniziata la sua chiamata al distruttore), quindi dovrebbe essere eseguito il distruttore di Foo2, che dovrebbe essere eseguito dopo il distruttore di Foo1. –

5

Tutte le istanze che vedo in questo codice sono statici.

Di conseguenza il loro distruttore viene chiamato alla fine dell'eseguibile, dopo che il main è finito.

se il distruttore non è stato chiamato, allora era un bug.

+3

Mi sembra che 'foo2' non sarà costruito fino alla chiusura del programma, nel momento in cui' anotherFoo' è destructed. – JasonD

+0

Nessuna delle istanze che vedo dovrebbe essere costruita prima di 'main()'. Sono tutti statici locali e dovrebbero essere costruiti la prima volta che entrano in gioco. –

+0

Mi porti qualche dubbio, controllerò la costruzione di istanze statiche ... Siamo sicuri. –

4

l'oggetto statico verrà distrutto quando il programma esiste. metti un punto di interruzione a ~Foo2(), lo vedrai o scriverai un log in un file per aiutarti a diagnosticarlo. Se davvero non viene chiamato, si tratta di un bug del compilatore.

enter image description here

Ed è divertente per caricare una foto per rispondere a una domanda.

+0

È divertente, ma quando i compilatori non sono d'accordo, le risposte devono fare riferimento a un'autorità superiore per eseguire il backup delle loro affermazioni: lo standard. –

+0

inoltre, sull'uso dei log, sta già usando stdout come log! –

+0

@MooingDuck ha aggiornato OP dopo il mio post :) – billz

4

C++ 11 3.6.3/1: distruttori per gli oggetti inizializzati [...] con durata di conservazione statica sono chiamati come risultato di ritorno da main

Nel punto in cui la il programma restituisce da main, anotherFoo è stato inizializzato; ma foo2 non ha, dal momento che non è stato inizializzato fino alla prima chiamata a GetFoo2 durante la distruzione di anotherFoo. Pertanto, un'interpretazione rigorosa delle regole implicherebbe che il suo distruttore non debba essere chiamato.

+0

O che il suo distruttore dovrebbe essere chiamato prima del costruttore. O qualsiasi altra cosa Penso che sia un caso di comportamento indefinito. –

+0

@JamesKanze: Certamente non implica che il distruttore debba essere chiamato prima del costruttore: "I distruttori ** per gli oggetti inizializzati ** [...] sono chiamati". –

+0

E l'oggetto viene inizializzato prima di lasciare il programma. (Ma sono d'accordo sul fatto che lo standard non è del tutto chiaro sul comportamento richiesto. Per questo motivo direi che è un comportamento indefinito.) –