2009-11-24 14 views
8

Durante la scrittura di questo:L'operatore condizionale dovrebbe valutare tutti gli argomenti?

1: inline double f(double arg) { 
2: return arg == 0.0 ? 0.0 : 1./arg; 
3: } 
4: const double d = f(0.0); 

Il Microsoft Visual Studio 2005 compilatore a 64 bit fornito con

line 4: warning C4723: potential divide by 0 

Mentre voi ed io possiamo vedere chiaramente che un div-by-zero è mai andare per accadere ...

o è?

+11

Fare attenzione, confrontando argomenti "doppi" sull'uguaglianza. La magia del male succede lì ... – SadSido

+1

No, non è così. Processo perfettamente definito. In particolare, '0.0 == -0.0'. Quindi, per tutti gli insiemi di valori per i quali '1./arg' è definito, sappiamo che' arg! = 0.0'. – MSalters

+1

@MSalters: ma a causa di errori di arrotondamento, arg potrebbe non essere 0.0 (o -0.0 per quella materia) quando ci si aspettava che lo fosse. – jalf

risposta

4

È un bug evidente, oltre ogni dubbio.

L'intento dell'avviso NON è di avvisare di tutte le divisioni in un programma. Sarebbe troppo rumoroso in qualsiasi programma ragionevole. Invece, l'intento è di avvisarti quando devi controllare un argomento. In questo caso, hai controllato l'argomento. Quindi, il compilatore dovrebbe averlo notato e stare zitto.

L'implementazione tecnica di tale funzione viene effettuata etichettando le variabili nei rami del codice con determinati attributi. Uno degli attributi più comuni è il tri-stato "Is null". Prima del ramo, arg è una variabile esterna e arg [[Isnull]] è sconosciuto. Ma dopo il controllo su arg ci sono due rami. Nel primo ramo arg [[Isnull]] è vero. Nel secondo ramo arg [[Isnull]] è falso.

Ora, quando si tratta di generare avvisi di puntatore nullo e zero, è necessario verificare l'attributo [[IsNull]. Se è vero, hai un grave avvertimento/errore. Se sconosciuto, dovresti generare l'avviso sopra indicato - un potenziale problema, oltre a quello che può dimostrare il compilatore. Ma in questo caso, l'attributo [[isNull]] è False. Il compilatore con la stessa logica formale degli umani, sa che non c'è alcun rischio.

Ma come facciamo a sapere che il compilatore utilizza internamente un attributo di tipo [[Isnull]]? Ricorda il primo paragrafo: senza di esso, dovrebbe o avvisare sempre o mai. Sappiamo che a volte avvisa, ergo ci deve essere un attributo [[IsNull]].

+0

Questo è un ragionamento solido. Posso conviverci, soprattutto perché dimostra la mia istintività. – xtofl

+0

Un recente errore di Linux è stato causato esattamente da questo attributo. Nel codice incriminato, un puntatore è stato prima dereferenziato (in pratica 'int * member = & (foo-> bar);' e quindi controllato 'if (! Foo) return;'. Tuttavia, a causa del dereferenziamento, il 'foo [ [IsNull]] l'attributo era già impostato su false e il controllo NULL è stato ottimizzato. – MSalters

11

Il compilatore non è in grado di analizzare staticamente tutti i percorsi di codice e tener conto di tutte le possibilità in ogni momento. Teoricamente, l'analisi completa del comportamento di un programma semplicemente esaminando il suo codice sorgente può fornire una soluzione al problema dell'arresto, che è indecidibile. I compilatori dispongono di un set limitato di regole di analisi statiche per rilevare le regole. Lo standard C++ non richiede al compilatore di emettere questo tipo di avvertimenti, quindi no. Non è un bug. È più come una caratteristica inesistente.

+0

Hai ragione. Non può mai essere un bug dal punto di vista standard del C++, poiché riguarda gli avvertimenti. Tuttavia, se questo "difetto" mi costringe a riscrivere il codice perfettamente valido (o a circondarlo con '# pragma's), da un punto di vista del 'programma per computer', è un bug. – xtofl

+0

@xtofl: La parola chiave nell'avvertimento è "potenziale" –

+0

Concordato, ma il problema principale è che cerchiamo di trattare gli avvertimenti come errori e non possiamo farlo con avvisi "falsi positivi". – xtofl

7

No, l'operatore condizionale non valuta entrambi gli argomenti. Tuttavia, una potenziale divisione per zero, se un compilatore è in grado di rilevare una cosa del genere, viene in genere segnalata. Non è per nulla che lo standard occupa ~ 2 pagine per descrivere il comportamento di questo operatore.

Da N-4411:

5,16 Operatore condizionale gruppo

espressioni condizionali da destra a sinistra. La prima espressione è convertita contestualmente in bool (clausola 4). Viene valutato e, se è vero, il risultato dell'espressione condizionale è il valore della seconda espressione , altrimenti quella della terza espressione . Solo una delle seconde e terze espressioni è valutata . Ogni computo del valore e l'effetto collaterale associato alla prima espressione viene sequenziato prima di ogni calcolo del valore e dell'effetto collaterale associato alla seconda o alla terza espressione .

Inoltre, nota:

Altrimenti, se la seconda e terza operando con tipi diversi, e né ha (possibilmente cv-qualificato) tipo di classe, si effettua un tentativo a convertire ciascuno di quegli operandi nel tipo dell'altro.

L'esempio che hai citato ha lo stesso tipo sia per la seconda che per la terza espressione - stai tranquillo, solo il primo sarà valutato.

+0

Dubito che il punto 3 in realtà dice che entrambi gli operandi saranno valutati. Penso che questo riguardi piuttosto cose come 'std :: string s (" A "); const char * c = "B"; std :: string a = cond()? s: c; const char * b = cond()? s: c; 'dovrebbe compilare o meno (determinare quale deve essere il tipo di risultato dell'operatore: il compilatore controlla cosa può essere lanciato su cosa - in questo caso il primo?: compila, poiché' const char *' può essere convertito implicitamente a std :: string, ma il secondo non viene compilato, poiché il tipo di risultato dell'operatore è 'std :: string', e questo non può essere convertito implicitamente in' const char * ') – UncleBens

+0

Accolto e modificato. Avrei dovuto specificarlo. – dirkgently

3

verrà generato il codice per la divisione, quindi l'avviso. ma il ramo non verrà mai preso quando arg è 0, quindi è sicuro.

3

L'operatore == per i numeri a virgola mobile non è sicuro (ad esempio non ci si può fidare a causa di problemi di arrotondamento). In questo caso specifico è effettivamente sicuro, quindi è possibile ignorare l'avviso, ma il compilatore non effettuerà tale analisi in base a un operatore i cui risultati sono alquanto imprevedibili nel caso generale.

3

L'operatore condizionale non deve valutare tutti gli argomenti. Ma credo che potresti prendere arg quasi uguale a 0, quindi arg == 0.0 sarà false, ma 1./arg darà "divisione per zero" risultato. Quindi penso che l'avvertimento sia utile qui.

A proposito, Visual C++ 2008 non fornisce questo avviso.

+0

Questo ha senso. Grazie. – xtofl

+1

Le divisioni in virgola mobile IEEE 754 in C non generano una divisione per zero. Il risultato della divisione per zero sarà + Infinito, -Infinity o NaN –

+0

Il risultato può essere + Infinito o -Infinito per i numeri subnormali, ma io chiamerei un overflow piuttosto che una divisione per zero. – starblue

0

In aggiunta agli altri commenti: l'avviso viene generato dal compilatore, il ramo morto viene rimosso dall'ottimizzatore che viene eseguito in seguito, eventualmente anche nella fase di collegamento.

Quindi no, non è un bug. L'avviso è un servizio aggiuntivo fornito dal compilatore, non richiesto dallo standard. È uno sfortunato effetto collaterale dell'architettura del compilatore/linker.

0

Potrebbe essere possibile evitare l'avviso utilizzando la parola chiave __assume specifica Microsoft. Non sono sicuro di poterlo collegare con l'operatore condizionale. Altrimenti qualcosa come

if (arg == 0.0){ 
    return 0.0; 
} 
else { 
__assume(arg != 0.0); 
    return 1./arg; 
} 

può valere la pena di scattare. O, ovviamente, basta tacitare l'avviso durante questa funzione, con l'appropriato #pragma.

Problemi correlati