2010-07-15 5 views
7

mi sono imbattuto nel seguente codice di recente:C++ legale/ben definito per chiamare un metodo non statico che non accede ai membri tramite un puntatore nullo?

class Foo 
{ 
public: 
    void bar(); 
    // .. other stuff 
}; 

void Foo::bar() 
{ 
    if(!this) { 
     // .. do some stuff without accessing any data members 
     return; 
    } 

    // .. do normal actions using data members 
} 

il codice viene compilato perché i metodi in C++ sono solo funzioni che vengono implicitamente passato un puntatore per 'questo' e 'questo' può essere controllato per essere NULL come qualsiasi altro puntatore. Ovviamente questo codice è confuso e di cattiva pratica anche se non si blocca; sarebbe piuttosto difficile passare attraverso il codice nel debugger, vedere un puntatore NULL sta per avere un metodo chiamato su di esso e quindi non vedere il crash previsto. La mia domanda è: viola lo standard C++ per chiamare SomeFooPtr->bar() dove SomeFooPtr == NULL?

Mi sembra che non sia perché l'operatore definito dall'utente-> restituisce un puntatore, il che significa che anche se quel puntatore è NULL non è stato sicuramente dereferenziato (dereferenziare un puntatore NULL sono sicuro che è considerato dallo standard come illegale o indefinito). D'altra parte la semantica dei puntatori grezzi non deve necessariamente corrispondere alla semantica dei puntatori definiti dall'utente - forse l'operatore-> su di essi è considerata una dereferenziazione anche se il compilatore non ne genererà uno.

+0

duplicati: http://stackoverflow.com/questions/2474018/when-does-invoking-a-member-function-on-a-null-instance-result-in-undefined-behav – GManNickG

risposta

13

Probabilmente funziona sulla maggior parte dei sistemi, ma è un comportamento non definito. Disse lo standard:

5.2.5.3

Se E1 ha il tipo “puntatore a classe X”, quindi l'espressione E1->E2 viene convertito in forma equivalente (*(E1)).E2 [...]

E:

5.2.5.1

Un'espressione postfissa seguita da un punto . o una freccia ->, facoltativamente seguita dalla parola chiave template (14.8.1), quindi seguita da un'espressione id , è un'espressione postfissa. L'espressione postfissa prima del punto o della freccia viene valutata; 58) [...]

58) Questa valutazione avviene anche se la conseguenza è necessario determinare il valore dell'intera espressione postfissa, per esempio se l'id-espressione denota un membro statico.

valutazione dei *x dove x è NULL risultati puntatore comportamento indefinito, così tuo è chiaramente un caso di UB, prima ancora inserito la funzione.

+0

Grazie, questo è esattamente quello che stavo cercando. –

+0

Ci scusiamo per le modifiche disordinate. Per prima cosa ho trascurato qualcosa nello Standard, quindi ho letto male la tua domanda. Dovrebbe essere ok ora. – Thomas

+0

Puoi dare un esempio di un'espressione del modulo 'A.template B'? Dov'è rilevante questa sintassi? – Philipp

1

Non importa se è legale, è fonte di confusione per il lettore. Nell'implementazione in cui funziona questo codice, si accede al vtable per tipo, certamente non per oggetto.

Inoltre, mi aspetto che questo codice sia stato inserito per coprire un errore del costruttore, che avrebbe mascherato una serie di problemi altrove. Il fallimento del costruttore deve essere gestito correttamente, non con un brutto espediente come nell'esempio.

1

Questo è (tutti insieme ora) comportamento non definito. Tuttavia, per molti compilatori funzionerà, con il vincolo aggiuntivo che il metodo deve essere non-virtuale.

1

È UB. Un buon modo per farlo andare in crash è usarlo come una classe base di una classe derivata che usa ereditarietà multipla. YMMV.

7

Questo test è rotto anche se non è stato un dereferenziare UB. Si rompe quando questo di rettifiche di valore per l'ereditarietà multipla entrano in gioco:

#include <stdio.h> 
class B 
{ 
    int value; 
    public: 
    void foo() 
    { 
     if (!this) 
      printf("this==0\n"); 
     else 
      printf("safe\n"); 
    } 
}; 
class A { public: int anotherValue; }; 
class Z : public A,public B {}; 

int main() 
{ 
    Z *z=0; 
    z->foo(); 
} 

stampe "sicuro" qui.

+0

Eccellente esempio! Grazie. – Vlad