Perché i puntatori non vengono inizializzati con NULL per impostazione predefinita?


118

Qualcuno può spiegare perché i puntatori non vengono inizializzati NULL?
Esempio:

  void test(){
     char *buf;
     if (!buf)
        // whatever
  }

Il programma non entrerebbe nel if perché bufnon è nullo.

Vorrei sapere perché, in quale caso abbiamo bisogno di una variabile con trash attivato, in particolare dei puntatori che indirizzano il cestino in memoria?


13
Bene, perché i tipi fondamentali non vengono inizializzati. Quindi presumo la tua "vera" domanda: è perché i tipi fondamentali non sono inizializzati?
GManNickG

11
"il programma non entrerebbe nel if perché buf non è nullo". Non è corretto. Dal momento che non si sa cosa buf è , non si può sapere che cosa non è .
Drew Dormann

A differenza di qualcosa come Java, il C ++ dà molta più responsabilità allo sviluppatore.
Rishi

interi, puntatori, il valore predefinito è 0 se si utilizza il costruttore ().
Erik Aronesty

A causa del presupposto che qualcuno che usa C ++ sappia cosa sta facendo, inoltre qualcuno che sta usando un puntatore grezzo su un puntatore intelligente sa (ancora di più) cosa sta facendo!
Lofty Lion

Risposte:


161

Sappiamo tutti che il puntatore (e altri tipi di POD) dovrebbe essere inizializzato.
La domanda diventa quindi "chi dovrebbe inizializzarli".

Bene, ci sono fondamentalmente due metodi:

  • Il compilatore li inizializza.
  • Lo sviluppatore li inizializza.

Supponiamo che il compilatore abbia inizializzato qualsiasi variabile non esplicitamente inizializzata dallo sviluppatore. Quindi ci imbattiamo in situazioni in cui l'inizializzazione della variabile non era banale e il motivo per cui lo sviluppatore non lo ha fatto al punto della dichiarazione era che doveva eseguire alcune operazioni e quindi assegnare.

Quindi ora abbiamo la situazione in cui il compilatore ha aggiunto un'istruzione extra al codice che inizializza la variabile a NULL, quindi in seguito viene aggiunto il codice dello sviluppatore per eseguire l'inizializzazione corretta. Oppure in altre condizioni la variabile potenzialmente non viene mai utilizzata. Molti sviluppatori C ++ griderebbero fallo in entrambe le condizioni al costo di quell'istruzione extra.

Non è solo questione di tempo. Ma anche spazio. Ci sono molti ambienti in cui entrambe le risorse sono preziose e nemmeno gli sviluppatori vogliono arrendersi.

MA : è possibile simulare l'effetto di forzare l'inizializzazione. La maggior parte dei compilatori ti avviserà delle variabili non inizializzate. Quindi imposto sempre il mio livello di avviso al massimo livello possibile. Quindi dite al compilatore di trattare tutti gli avvisi come errori. In queste condizioni, la maggior parte dei compilatori genererà quindi un errore per le variabili non inizializzate ma utilizzate e quindi impedirà la generazione del codice.


5
Bob Tabor ha detto: "Troppe persone non hanno pensato abbastanza all'inizializzazione!" È "facile" inizializzare automaticamente tutte le variabili, ma richiede tempo e programmi lenti non sono "amichevoli". Un foglio di calcolo o editor che mostrassero la spazzatura casuale trovata da malloc sarebbero inaccettabili. C, uno strumento affilato per utenti addestrati (pericoloso se utilizzato in modo improprio) non dovrebbe richiedere tempo per inizializzare le variabili automatiche. Una macro della ruota di allenamento per inizializzare le variabili potrebbe essere, ma molti pensano che sia meglio alzarsi in piedi, essere consapevoli e sanguinare un po '. In un pizzico, lavori nel modo in cui ti eserciti. Quindi pratica come vorresti che fosse.
Bill IV

2
Saresti sorpreso di quanti bug verrebbero evitati solo se qualcuno risolvesse tutta la loro inizializzazione. Questo sarebbe un lavoro noioso se non fosse per gli avvisi del compilatore.
Jonathan Henson,

4
@ Loki, ho difficoltà a seguire il tuo punto. Stavo solo cercando di lodare la tua risposta come utile, spero che tu l'abbia capito. Altrimenti mi dispiace.
Jonathan Henson,

3
Se il puntatore viene prima impostato su NULL e quindi impostato su qualsiasi valore, il compilatore dovrebbe essere in grado di rilevarlo e ottimizzare la prima inizializzazione NULL, giusto?
Korchkidu

1
@Korchkidu: a volte. Uno dei problemi principali però è che non c'è modo di avvisarti che hai dimenticato di eseguire l'inizializzazione, poiché non può sapere che l'impostazione predefinita non è perfetta per il tuo utilizzo.
Deduplicator

41

Citando Bjarne Stroustrup in TC ++ PL (Special Edition p.22):

L'implementazione di una funzionalità non dovrebbe imporre costi generali significativi ai programmi che non la richiedono.


e non dare nemmeno l'opzione. Sembra
Jonathan

8
@ Jonathan nulla ti impedisce di inizializzare il puntatore a null o a 0 come è standard in C ++.
stefanB

8
Sì, ma Stroustrup avrebbe potuto fare in modo che la sintassi predefinita favorisse la correttezza del programma piuttosto che le prestazioni inizializzando a zero il puntatore e costringendo il programmatore a richiedere esplicitamente che il puntatore non fosse inizializzato. Dopotutto, la maggior parte delle persone preferisce il corretto ma lento rispetto al veloce ma sbagliato, in quanto è generalmente più facile ottimizzare una piccola quantità di codice piuttosto che correggere i bug nell'intero programma. Soprattutto quando gran parte di esso può essere fatto da un compilatore decente.
Robert Tuck

1
Non rompe la compatibilità. L'idea è stata considerata insieme a "int * x = __uninitialized": sicurezza per impostazione predefinita, velocità per intenzione.
MSalters

4
Mi piace quello che Dfa. Se non vuoi l'inizializzazione usa questa sintassi float f = void;o int* ptr = void;. Ora è inizializzato di default ma se ne hai davvero bisogno puoi fermare il compilatore dal farlo.
deft_code

23

Perché l'inizializzazione richiede tempo. E in C ++, la prima cosa che dovresti fare con qualsiasi variabile è inizializzarla esplicitamente:

int * p = & some_int;

o:

int * p = 0;

o:

class A {
   public:
     A() : p( 0 ) {}  // initialise via constructor
   private:
     int * p;
};

1
k, se l'inizializzazione richiede tempo e lo voglio ancora, è comunque possibile rendere nulli i miei puntatori senza impostarlo manualmente? vedi, non perché non voglio correggerlo, perché sembra che non userò mai puntatori unitiliaz con spazzatura sul loro indirizzo
Jonathan

1
Inizializzi i membri della classe nel costruttore della classe: è così che funziona C ++.

3
@ Jonathan: ma anche null è spazzatura. Non puoi fare nulla di utile con un puntatore nullo. Dereferenziarne uno è altrettanto un errore. Crea puntatori con valori corretti, non null.
DrPizza

2
L'inizializzazione di un puntatore su Nnull può essere una cosa sensata da fare e ci sono diverse operazioni che puoi eseguire sui puntatori nulli: puoi testarli e puoi chiamare delete su di essi.

4
Se non utilizzerai mai un puntatore senza inizializzarlo esplicitamente, non importa cosa conteneva prima di assegnargli un valore, e secondo il principio C e C ++ di pagare solo per ciò che usi, non è fatto automaticamente. Se è presente un valore predefinito accettabile (solitamente il puntatore nullo), è necessario inizializzarlo. Puoi inizializzarlo o lasciarlo non inizializzato, a tua scelta.
David Thornley

20

Perché uno dei motti di C ++ è:


Non paghi per quello che non usi


Proprio per questo motivo, il operator[]della vectorclasse non controlla se l'indice è fuori limite, ad esempio.


12

Per ragioni storiche, principalmente perché è così che è fatto in C. Perché è fatto così in C, è un'altra questione, ma penso che il principio zero overhead sia stato coinvolto in qualche modo in questa decisione progettuale.


Immagino perché il C è considerato un linguaggio di livello inferiore con un facile accesso alla memoria (ovvero i puntatori), quindi ti dà la libertà di fare quello che vuoi e non impone il sovraccarico inizializzando tutto. A proposito, penso che dipenda dalla piattaforma perché ho lavorato su una piattaforma mobile basata su Linux che ha inizializzato tutta la sua memoria su 0 prima dell'uso, quindi tutte le variabili sarebbero state impostate su 0.
stefanB

8

Inoltre, abbiamo un avvertimento per quando lo fai saltare: "è possibilmente usato prima di assegnare un valore" o parole simili a seconda del tuo compilatore.

Compili con avvisi, giusto?


Ed è possibile solo come riconoscimento che la traccia dei compilatori potrebbe essere difettosa.
Deduplicator

6

Ci sono praticamente poche situazioni in cui ha senso che una variabile non venga inizializzata e l'inizializzazione predefinita ha un piccolo costo, quindi perché farlo?

C ++ non è C89. Diavolo, anche C non è C89. È possibile combinare dichiarazioni e codice, quindi è necessario rimandare la dichiarazione fino a quando non si dispone di un valore adatto con cui inizializzare.


2
Quindi solo ogni valore dovrà essere scritto due volte: una volta dalla routine di installazione del compilatore e di nuovo dal programma dell'utente. Di solito non è un grosso problema, ma si somma (ad esempio, se stai creando un array di 1 milione di elementi). Se vuoi l'auto-inizializzazione puoi sempre creare i tuoi tipi che lo fanno; ma in questo modo non sei costretto ad accettare overhead inutili se non vuoi.
Jeremy Friesner

3

Un puntatore è solo un altro tipo. Se si crea un int, charo digita qualsiasi altro POD non è inizializzato a zero, quindi perché dovrebbe un puntatore? Questo potrebbe essere considerato un sovraccarico non necessario per qualcuno che scrive un programma come questo.

char* pBuf;
if (condition)
{
    pBuf = new char[50];
}
else
{
    pBuf = m_myMember->buf();
}

Se sai che stai per inizializzarlo, perché il programma dovrebbe sostenere un costo quando crei pBufper la prima volta all'inizio del metodo? Questo è il principio zero overhead.


1
d'altra parte potresti fare char * pBuf = condition? nuovo carattere [50]: m_myMember-> buf (); È più una questione di sintassi che di efficienza, ma sono comunque d'accordo con te.
the_drow

1
@ the_drow: Beh, si può renderlo più complesso solo in modo che una tale riscrittura non sia possibile.
Deduplicator

2

Se vuoi un puntatore che sia sempre inizializzato su NULL, puoi usare un modello C ++ per emulare quella funzionalità:

template<typename T> class InitializedPointer
{
public:
    typedef T       TObj;
    typedef TObj    *PObj;
protected:
    PObj        m_pPointer;

public:
    // Constructors / Destructor
    inline InitializedPointer() { m_pPointer=0; }
    inline InitializedPointer(PObj InPointer) { m_pPointer = InPointer; }
    inline InitializedPointer(const InitializedPointer& oCopy)
    { m_pPointer = oCopy.m_pPointer; }
    inline ~InitializedPointer() { m_pPointer=0; }

    inline PObj GetPointer() const  { return (m_pPointer); }
    inline void SetPointer(PObj InPtr)  { m_pPointer = InPtr; }

    // Operator Overloads
    inline InitializedPointer& operator = (PObj InPtr)
    { SetPointer(InPtr); return(*this); }
    inline InitializedPointer& operator = (const InitializedPointer& InPtr)
    { SetPointer(InPtr.m_pPointer); return(*this); }
    inline PObj operator ->() const { return (m_pPointer); }
    inline TObj &operator *() const { return (*m_pPointer); }

    inline bool operator!=(PObj pOther) const
    { return(m_pPointer!=pOther); }
    inline bool operator==(PObj pOther) const
    { return(m_pPointer==pOther); }
    inline bool operator!=(const InitializedPointer& InPtr) const
    { return(m_pPointer!=InPtr.m_pPointer); }
    inline bool operator==(const InitializedPointer& InPtr) const
    { return(m_pPointer==InPtr.m_pPointer); }

    inline bool operator<=(PObj pOther) const
    { return(m_pPointer<=pOther); }
    inline bool operator>=(PObj pOther) const
    { return(m_pPointer>=pOther); }
    inline bool operator<=(const InitializedPointer& InPtr) const
    { return(m_pPointer<=InPtr.m_pPointer); }
    inline bool operator>=(const InitializedPointer& InPtr) const
    { return(m_pPointer>=InPtr.m_pPointer); }

    inline bool operator<(PObj pOther) const
    { return(m_pPointer<pOther); }
    inline bool operator>(PObj pOther) const
    { return(m_pPointer>pOther); }
    inline bool operator<(const InitializedPointer& InPtr) const
    { return(m_pPointer<InPtr.m_pPointer); }
    inline bool operator>(const InitializedPointer& InPtr) const
    { return(m_pPointer>InPtr.m_pPointer); }
};

1
Se dovessi implementarlo, non mi preoccuperei di te copy ctor o dell'operazione di assegnazione - i valori predefiniti sono abbastanza OK. E il tuo distruttore è inutile. Ovviamente puoi anche testare i puntatori usando less di operater et all) in alcune circostanze) quindi dovresti fornirli.

OK, meno di quanto sia banale da implementare. Avevo il distruttore in modo che se l'oggetto esce dall'ambito (cioè definito locale all'interno di un sottoambito di una funzione) ma occupa ancora spazio sullo stack, la memoria non viene lasciata come un puntatore penzolante a spazzatura. Ma amico seriamente, l'ho scritto in meno di 5 minuti. Non è pensato per essere perfetto.
Adisak

OK ha aggiunto tutti gli operatori di confronto. Le sostituzioni predefinite possono essere ridondanti ma sono qui esplicitamente poiché questo è un esempio.
Adisak

1
Non riuscivo a capire come questo renderebbe nulli tutti i puntatori senza impostarli manualmente, potresti spiegare cosa hai fatto qui per favore?
Jonathan

1
@ Jonathan: Questo è fondamentalmente un "puntatore intelligente" che non fa altro che impostare il puntatore su null. IE invece di Foo *a, usi InitializedPointer<Foo> a- Un esercizio puramente accademico in quanto Foo *a=0meno battitura. Tuttavia, il codice sopra è molto utile da un punto di vista educativo. Con una piccola modifica (al ctor / dtor "placeholding" e alle operazioni di assegnazione), potrebbe essere facilmente esteso a vari tipi di puntatori intelligenti inclusi i puntatori con ambito (che sono gratuiti sul distruttore) e puntatori conteggio dei riferimenti aggiungendo inc / dec operazioni quando m_pPointer è impostato o cancellato.
Adisak

2

Nota che i dati statici sono inizializzati a 0 (a meno che tu non dica diversamente).

E sì, dovresti sempre dichiarare le tue variabili il più tardi possibile e con un valore iniziale. Codice come

int j;
char *foo;

dovrebbe far scattare un campanello d'allarme quando lo leggi. Non so se qualche lanugine può essere persuasa a lamentarsene, dato che è legale al 100%.


è GARANTITO o è solo una pratica comune usata dai compilatori di oggi?
gha.st

1
le variabili statiche sono inizializzate a 0, il che fa la cosa giusta anche per i puntatori (cioè, le imposta a NULL, non tutti i bit 0). Questo comportamento è garantito dallo standard.
Alok Singhal

1
l'inizializzazione dei dati statici a zero è garantita dagli standard C e C ++, non è solo pratica comune
groovingandi

1
forse perché alcune persone vogliono assicurarsi che il loro stack sia ben allineato, dichiarano preventivamente tutte le variabili all'inizio della funzione? Forse stanno scrivendo in un dialetto che RICHIEDE questo?
KitsuneYMG

1

Un altro possibile motivo è che in fase di collegamento ai puntatori viene assegnato un indirizzo, ma l'indirizzamento / de-referenziamento indiretto di un puntatore è responsabilità del programmatore. Di solito, al compilatore non interessa di meno, ma il compito di gestire i puntatori e di assicurarsi che non si verifichino perdite di memoria viene trasferito al programmatore.

In realtà, in poche parole, sono inizializzati nel senso che al momento del collegamento alla variabile pointer viene assegnato un indirizzo. Nel codice di esempio sopra, è garantito che si arresti in modo anomalo o generi un SIGSEGV.

Per motivi di sanità mentale, inizializza sempre i puntatori a NULL, in questo modo se qualsiasi tentativo di dereferenziarlo senza malloco newindurrà il programmatore nel motivo per cui il programma si è comportato male.

Spero che questo aiuti e abbia un senso,


0

Bene, se C ++ inizializzasse i puntatori, allora la gente di C che si lamenta "C ++ è più lento di C" avrebbe qualcosa di reale a cui aggrapparsi;)


Non è la mia ragione. La mia ragione è che se l'hardware ha 512 byte di ROM e 128 byte di RAM e un'istruzione in più per azzerare un puntatore è anche un byte che è una percentuale abbastanza grande dell'intero programma. Ho bisogno di quel byte!
Jerry Jeremiah

0

Il C ++ proviene da uno sfondo C e ci sono alcuni motivi che tornano da questo:

C, anche più di C ++ è una sostituzione del linguaggio assembly. Non fa nulla che tu non gli dica di fare. Perciò: se vuoi NULLO, fallo!

Inoltre, se annulli le cose in un linguaggio bare-metal come il C, sorgono automaticamente domande sulla coerenza: se malloc qualcosa, dovrebbe essere azzerato automaticamente? Che ne dici di una struttura creata sullo stack? tutti i byte dovrebbero essere azzerati? E le variabili globali? che dire di una dichiarazione come "(* 0x18);" non significa allora che la posizione di memoria 0x18 dovrebbe essere azzerata?


In realtà, in C, se vuoi allocare memoria completamente zero puoi usare calloc().
David Thornley

1
solo il mio punto - se vuoi farlo, puoi, ma non è fatto per te automagicamente
gha.st

0

Quali sono questi suggerimenti di cui parli?

Per la sicurezza rispetto alle eccezioni, utilizzare sempre auto_ptr, shared_ptr, weak_ptre le loro altre varianti.
Un segno distintivo di un buon codice è quello che non include una singola chiamata a delete.


3
A partire da C ++ 11, evita auto_ptre sostituisci unique_ptr.
Deduplicator

-2

Oh ragazzo. La vera risposta è che è facile azzerare la memoria, che è un'inizializzazione di base per esempio un puntatore. Il che non ha nulla a che fare con l'inizializzazione dell'oggetto stesso.

Considerando gli avvertimenti che la maggior parte dei compilatori fornisce ai livelli più alti, non riesco a immaginare di programmare al livello più alto e di trattarli come errori. Dato che alzarli non mi ha mai salvato nemmeno un bug in enormi quantità di codice prodotto, non posso raccomandarlo.


Se non ci si aspetta che il puntatore sia NULL, inizializzarlo su quello è altrettanto un errore.
Deduplicatore
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.