2015-10-28 7 views
14

mi sono imbattuto in un interessante regola sicura codifica in C++ in cui si afferma:C++ con un comportamento indefinito, compilatore genera std :: eccezione

Do not reenter a function during the initialization of a static variable declaration. If a function is reentered during the constant initialization of a static object inside that function, the behavior of the program is undefined. Infinite recursion is not required to trigger undefined behavior, the function need only recur once as part of the initialization.

L'esempio non_compliant dello stesso è:

#include <stdexcept> 

int fact(int i) noexcept(false) { 
    if (i < 0) { 
    // Negative factorials are undefined. 
    throw std::domain_error("i must be >= 0"); 
    } 

    static const int cache[] = { 
    fact(0), fact(1), fact(2), fact(3), fact(4), fact(5), 
    fact(6), fact(7), fact(8), fact(9), fact(10), fact(11), 
    fact(12), fact(13), fact(14), fact(15), fact(16) 
    }; 

    if (i < (sizeof(cache)/sizeof(int))) { 
    return cache[i]; 
    } 

    return i > 0 ? i * fact(i - 1) : 1; 
} 

che secondo la fonte dà l'errore:

terminate called after throwing an instance of '__gnu_cxx::recursive_init_error' 
    what(): std::exception 

quando eseguito in Visual Studio 2013. Ho provato un codice simile e ho ottenuto lo stesso errore (compilato usando g ++ ed eseguito, su Ubuntu).

Sono dubbioso se la mia comprensione è corretta rispetto a questo concetto poiché non sono esperto di C++. Secondo me, dato che l'array di cache è costante, il che significa che può essere di sola lettura e deve essere inizializzato una sola volta come statico, viene inizializzato di nuovo e di nuovo poiché i valori di questo array sono il valore restituito da ciascuno dei chiamate di funzioni ricorsive separate da virgola contro il comportamento dell'array dichiarato. Quindi, dà un comportamento indefinito che è anche indicato nella regola.

Qual è una spiegazione migliore per questo?

+4

Sto capendo correttamente: vuoi la ragione dipendente dall'implementazione perché questo determinato caso di comportamento indefinito si comporta in questo modo particolare? – Downvoter

+0

@cad: hai ragione! .... :) –

risposta

17

Per eseguire fact(), è necessario innanzitutto inizializzare staticamente fact::cache[]. Al fine di fact::cache inizialmente, è necessario eseguire fact(). C'è una dipendenza circolare lì, che porta al comportamento che vedi. cache verrà inizializzato solo una volta, ma richiede di essere inizializzato per inizializzarsi. Anche scrivere questo mi fa girare la testa.

Il modo giusto per introdurre una tabella di cache come questo è per separarlo in una funzione diversa:

int fact(int i) noexcept(false) { 
    if (i < 0) { 
    // Negative factorials are undefined. 
    throw std::domain_error("i must be >= 0"); 
    } 

    return i > 0 ? i * fact(i - 1) : 1; 
} 

int memo_fact(int i) noexcept(false) { 
    static const int cache[] = { 
    fact(0), fact(1), fact(2), fact(3), fact(4), fact(5), 
    fact(6), fact(7), fact(8), fact(9), fact(10), fact(11), 
    fact(12), fact(13), fact(14), fact(15), fact(16) 
    }; 

    if (i < (sizeof(cache)/sizeof(int))) { 
    return cache[i]; 
    } 
    else { 
    return fact(i); 
    }  
} 

Qui, memo_fact::cache[] vengono inizializzati solo una volta - ma la sua inizializzazione non dipende su se stessa. Quindi non abbiamo alcun problema.

+0

grazie per aver spiegato :) .. La mia comprensione da questo è che l'array di cache che deve essere inizializzato staticamente una volta viene reinizializzato ripetutamente a causa della sua dipendenza dalla funzione ricorsiva chiamata fact(). Questa funzione, quindi, reinserisce la sua inizializzazione. –

6

Il C++ standard §6.7/4, dice quanto segue riguardo l'inizializzazione delle variabili di blocco-scope con la durata di archiviazione statica: è dato

If control re-enters the declaration recursively while the variable is being initialized, the behavior is undefined.

L'esempio seguente informativa:

int foo(int i) { 
static int s = foo(2*i); // recursive call - undefined 
return i+1; 
} 

Questo vale anche per il tuo esempio. fact(0) è una chiamata ricorsiva, quindi viene reinserita la dichiarazione di cache. Viene invocato un comportamento non definito.

È importante ricordare cosa significa comportamento indefinito. Comportamento non definito significa che può accadere tutto ciò che è e che "tutto" include in modo naturale le eccezioni generate.

Un comportamento indefinito significa anche che non è più possibile ragionare su nient'altro nel codice, tranne quando si vuole veramente scendere ai dettagli dell'implementazione del compilatore. Ma allora non stai più parlando di C++ in termini di utilizzo di un linguaggio di programmazione, ma in termini di come implementare quel linguaggio.

+0

grazie per aver spiegato :) ... –