Domanda

Considera il seguente codice:

#include <iostream>

struct foo
{
    // (a):
    void bar() { std::cout << "gman was here" << std::endl; }

    // (b):
    void baz() { x = 5; }

    int x;
};

int main()
{
    foo* f = 0;

    f->bar(); // (a)
    f->baz(); // (b)
}

Ci aspettiamo che (b) andare in crash, perché non esiste un membro corrispondente x per il puntatore nullo.In pratica, (a) non si blocca perché il this il puntatore non viene mai utilizzato.

Perché (b) dereferenzia il this puntatore ((*this).x = 5;), E this è nullo, il programma assume un comportamento indefinito, poiché il dereferenziamento nullo viene sempre considerato un comportamento indefinito.

Fa (a) comportare un comportamento indefinito?E se entrambe le funzioni (e x) sono statici?

È stato utile?

Soluzione

Sia (a) e risultato (b) in un comportamento indefinito. E 'sempre indefinito comportamento per chiamare una funzione membro tramite un puntatore nullo. Se la funzione è statico, è tecnicamente definito come bene, ma c'è qualche controversia.


La prima cosa da capire è perché è un comportamento indefinito di dereference un puntatore nullo. In C ++ 03, c'è in realtà un po 'di ambiguità.

Anche se "dereferenziazione un NULL pointer risultati in un comportamento indefinito" è menzionato nelle note sia in §1.9 / 4 e §8.3.2 / 4, non è mai esplicitamente dichiarato. (Le note sono non-normativo).

Tuttavia, si può cercare di dedurre dalla §3.10 / 2:

  

Un lvalue si riferisce ad un oggetto o funzione.

Quando dereferenziazione, il risultato è un lvalue. Un puntatore nullo non riferisce ad un oggetto, quindi quando si usa l'Ivalue abbiamo indefinito comportamento. Il problema è che la frase precedente non viene mai dichiarato, così che cosa significa "usare" il lvalue? Basta anche generare affatto, o di usarlo nel senso più formale di eseguire lvalue-to-rvalue conversione?

Indipendentemente da ciò, sicuramente non può essere convertito in un rvalue (§4.1 / 1):

  

Se l'oggetto a cui il lvalue riferisce non è un oggetto di tipo T e non è un oggetto di un tipo derivato da T, o se l'oggetto è inizializzato, un programma che richiede questa conversione è indefinito comportamento.

Qui si tratta di un comportamento sicuramente non definito.

L'ambiguità viene da se o non è un comportamento indefinito di deferenza ma non usare il valore da un puntatore non valido (cioè, ottenere un lvalue ma non convertirlo in un rvalue). Se no, allora int *i = 0; *i; &(*i); è ben definito. Si tratta di un problema attivo .

Quindi abbiamo un rigoroso "dereference un puntatore nullo, ottenere un comportamento indefinito" vista e un debole "utilizzare un puntatore nullo dereferenziati, ottenere un comportamento indefinito" vista.

Ora consideriamo la questione.


Sì, i risultati (a) in un comportamento indefinito. Infatti, se this è nullo allora indipendentemente dal contenuto della funzione il risultato è indefinito.

Ciò deriva dal §5.2.5 / 3:

  

Se E1 ha il tipo “puntatore alla classe X”, allora il E1->E2 espressione viene convertito alla forma (*(E1)).E2; equivalente

*(E1) comporterà comportamento indefinito con una stretta interpretazione, e .E2 converte in rvalue, rendendo comportamento indefinito per l'interpretazione deboli.

Ne consegue anche che è un comportamento indefinito direttamente da (§9.3.1 / 1):

  

Se una funzione membro non statica di una classe X è chiamato per un oggetto che non è di tipo X, o di un tipo derivato da X, il comportamento è indefinito.


Con funzioni statiche, il rigoroso rispetto all'interpretazione debole fa la differenza. A rigor di termini, non è definito:

  

Un membro statico può essere denominato utilizzando la sintassi di accesso membro della classe, nel qual caso l'oggetto-espressione viene valutata.

Cioè, è valutato solo come se fosse non statico e noi ancora una volta dereference un puntatore nullo con (*(E1)).E2.

Tuttavia, poiché E1 non viene utilizzato in una chiamata membro-funzione statica, se si usa l'interpretazione debole la chiamata è ben definito. Risultati *(E1) in un lvalue, la funzione statica è risolto, *(E1) viene scartato, e la funzione viene richiamata. Non v'è alcun lvalue-to-rvalue conversione, quindi non c'è un comportamento indefinito.

In C ++ 0x, come di n3126, l'ambiguità rimane. Per ora, essere al sicuro:. Utilizzare l'interpretazione restrittiva

Altri suggerimenti

Ovviamente indefinito significa che lo è non definito, ma a volte può essere prevedibile.Non si dovrebbe mai fare affidamento sulle informazioni che sto per fornire per il funzionamento del codice poiché certamente non sono garantite, ma potrebbero tornare utili durante il debug.

Potresti pensare che chiamare una funzione su un puntatore a un oggetto dereferenzia il puntatore e causa UB.In pratica se la funzione non è virtuale, il compilatore l'avrà convertita in una semplice chiamata di funzione passando il puntatore come primo parametro Questo, aggirando il dereferenziamento e creando una bomba a orologeria per la funzione membro chiamata.Se la funzione membro non fa riferimento ad alcuna variabile membro o funzione virtuale, potrebbe effettivamente avere esito positivo senza errori.Ricorda che il successo rientra nell'universo dell'“indefinito”!

La funzione MFC di Microsoft GetSafeHwnd in realtà si basa su questo comportamento.Non so cosa stessero fumando.

Se stai chiamando una funzione virtuale, il puntatore deve essere dereferenziato per arrivare a vtable e sicuramente otterrai UB (probabilmente un arresto anomalo ma ricorda che non ci sono garanzie).

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