Usi NULL o 0 (zero) per i puntatori in C ++?


194

All'inizio di C ++ quando era imbullonato sopra C, non era possibile usare NULL come definito (void*)0. Non è possibile assegnare NULL a nessun puntatore diverso da void*, il che lo ha reso in qualche modo inutile. In quei giorni, era accettato che tu usassi 0(zero) per puntatori null.

Fino ad oggi, ho continuato a usare zero come puntatore null, ma quelli intorno a me insistono per usarlo NULL. Personalmente non vedo alcun vantaggio nel dare un nome ( NULL) a un valore esistente - e poiché mi piace anche testare i puntatori come valori di verità:

if (p && !q)
  do_something();

quindi l'uso di zero ha più senso (come in se si utilizza NULL, non è possibile utilizzare logicamente p && !q- è necessario confrontare esplicitamente NULL, a meno che non si supponga che NULLsia zero, nel qual caso perché utilizzare NULL).

C'è qualche motivo oggettivo per preferire zero a NULL (o viceversa), o è solo una preferenza personale?

Modifica: dovrei aggiungere (e intendevo dire originariamente) che con RAII ed eccezioni, uso raramente puntatori zero / NULL, ma a volte ne hai ancora bisogno.


9
aspetta, non è necessario un puntatore null per valutare come falso indipendentemente dal fatto che null sia zero internamente o no?
Mooing Duck,

Risposte:


186

Ecco il punto di vista di Stroustrup su questo: domande frequenti su stile e tecnica C ++

In C ++, la definizione di NULLè 0, quindi c'è solo una differenza estetica. Preferisco evitare le macro, quindi uso 0. Un altro problema NULLè che le persone a volte credono erroneamente che sia diverso da 0 e / o non un numero intero. Nel codice pre-standard, NULLera / è talvolta definito qualcosa di inadatto e quindi doveva / deve essere evitato. Oggi è meno comune.

Se devi nominare il puntatore null, chiamalo nullptr; questo è ciò che viene chiamato in C ++ 11. Quindi, nullptrsarà una parola chiave.

Detto questo, non sudare le piccole cose.


7
Bjarne l'ha scritto prima che C ++ 0x iniziasse a lavorare su un nuovo tipo null. Sarà il caso che NULL verrà utilizzato per questo tipo quando è disponibile per una piattaforma, e penso che vedrai un C-change nel consenso generale su questo.
Richard Corden,

122

Ci sono alcuni argomenti (uno dei quali è relativamente recente) che credo contraddicano la posizione di Bjarne su questo.

  1. Documentazione di intenti

L'utilizzo NULLconsente di effettuare ricerche sul suo utilizzo ed evidenzia inoltre che lo sviluppatore voleva utilizzare un NULLpuntatore, indipendentemente dal fatto che il compilatore lo interpretasse NULLo meno.

  1. Il sovraccarico di puntatore e 'int' è relativamente raro

L'esempio che tutti citano è:

void foo(int*);
void foo (int);

void bar() {
  foo (NULL);  // Calls 'foo(int)'
}

Tuttavia, almeno secondo me, il problema con quanto sopra non è che stiamo usando NULL per la costante puntatore null, è che abbiamo sovraccarichi di "pippo" che accettano tipi di argomenti molto diversi. Anche il parametro deve essere un int, poiché qualsiasi altro tipo si tradurrà in una chiamata ambigua e genererà quindi un utile avviso del compilatore.

  1. Gli strumenti di analisi possono aiutare OGGI!

Anche in assenza di C ++ 0x, oggi sono disponibili strumenti che verificano che NULLvenga utilizzato per i puntatori e che 0venga utilizzato per i tipi integrali.

  1. C ++ 11 avrà un nuovo std::nullptr_ttipo.

Questo è l'argomento più recente della tabella. Il problema di 0e NULLviene attivamente affrontato per C ++ 0x e puoi garantire che per ogni implementazione che fornisce NULL, la prima cosa che faranno è:

#define NULL  nullptr

Per coloro che utilizzano NULLpiuttosto che 0, il cambiamento sarà un miglioramento nel tipo di sicurezza con poco o nessuno sforzo - se qualcosa si può anche prendere qualche bug dove sono utilizzati NULLper 0. Per chiunque usi 0oggi .... erm ... beh, si spera abbiano una buona conoscenza delle espressioni regolari ...


1
Questi sono alcuni punti piuttosto positivi, devo ammetterlo. Sono contento che C ++ 0x avrà un tipo null, penso che renderà molte cose più pulite.
Rob,

2
@Richard, perché non fare il contrario? Puoi usare Meyers nullptr_t quindi quando 0x diventa disponibile rimuovi #includee tieni al sicuro fino in fondo.
fnieto - Fernando Nieto,

15
#define NULL nullptrsembra pericoloso. Nel bene e nel male, molti codici legacy usano NULL per cose diverse da 0. Ad esempio, gli handle sono spesso implementati come un tipo integrale e impostarli su NULLnon è raro. Ho anche visto degli abusi come usare NULLper impostare un chara zero-terminator.
Adrian McCarthy,

8
@AdrianMcCarthy: direi che era pericoloso solo se c'era il pericolo che il codice si compilasse silenziosamente e avesse un significato diverso. Sono abbastanza sicuro che non sia così, quindi in effetti verrebbero rilevati tutti gli usi errati di NULL.
Richard Corden,

3
@RichardCorden: Uhm, supponendo che quegli altri usi di NULLsiano effettivamente errati. Molte API sono usate da tempo NULLcon le maniglie, e questo è in realtà l'uso documentato con molte di esse. Non è pragmatico rompere improvvisamente quelli e dichiarare che stanno sbagliando.
Adrian McCarthy,

45

Usa NULL. NULL mostra il tuo intento. Che sia 0 è un dettaglio di implementazione che non dovrebbe importare.


28
0 non è un dettaglio di implementazione. Lo standard definisce 0 per essere qualunque bit pattern rappresenti un puntatore nullo.
Ferruccio,

5
Come se ..!! Amico, C ++ è un linguaggio di basso livello! Usa 0, è un linguaggio ben noto.
hasen

8
Capisco che fa parte dello standard. È un dettaglio di implementazione per quanto riguarda la lettura del codice. Il lettore dovrebbe pensare "puntatore NULL" non "0 che in questo caso significa puntatore NULL, non un numero con cui potrei fare l'aritmetica."
Andy Lester,

2
+1. D'accordo con Andy. @Ferruccio, Il dettaglio dell'implementazione dell'idea del programmatore non è lo stesso dell'implementazione del compilatore definita
utente

se usi NULL, in un codice semplice senza intestazione complessa, troverai l'errore "NULL non definito in questo ambito" ..
ArtificiallyIntelligence

38

Uso sempre:

  • NULL per i puntatori
  • '\0' per i caratteri
  • 0.0 per galleggianti e doppi

dove 0 andrebbe bene. Si tratta di segnalare l'intenzione. Detto questo, non ne sono anale.


25
probabilmente dovresti usare 0.0F per float, per evitare il typecast implicito
EvilTeach

35

Ho smesso di usare NULL a favore di 0 molto tempo fa (così come la maggior parte delle altre macro). L'ho fatto non solo perché volevo evitare il più possibile le macro, ma anche perché NULL sembra essere stato utilizzato in modo eccessivo nei codici C e C ++. Sembra essere usato ogni volta che è necessario un valore 0, non solo per i puntatori.

Sui nuovi progetti, ho inserito questo in un'intestazione del progetto:

static const int nullptr = 0;

Ora, quando arrivano i compilatori conformi a C ++ 0x, tutto quello che devo fare è rimuovere quella riga. Un buon vantaggio di ciò è che Visual Studio riconosce già nullptr come parola chiave e la evidenzia in modo appropriato.


4
L'uso di NULL sarà più portatile a lungo termine. 'nullptr' sarà disponibile per alcune piattaforme e non per altre. La tua soluzione qui richiede di usare il preprocessore attorno alla tua dichiarazione per assicurarti che sia presente solo quando richiesto. NULL lo farà automaticamente.
Richard Corden,

6
Non sono d'accordo. Sarà meno portatile a breve termine fino a quando i compilatori non raggiungeranno. A lungo termine, sarà altrettanto portatile e forse un po 'più leggibile.
Ferruccio,

4
Inoltre puoi sempre #define nullptr NULL per il tuo compilatore non C ++ 0x.
Anteru,

20
    cerr << sizeof(0) << endl;
    cerr << sizeof(NULL) << endl;
    cerr << sizeof(void*) << endl;

    ============
    On a 64-bit gcc RHEL platform you get:
    4
    8
    8
    ================

La morale della storia. Dovresti usare NULL quando hai a che fare con i puntatori.

1) Dichiara il tuo intento (non farmi cercare in tutto il codice cercando di capire se una variabile è un puntatore o un tipo numerico).

2) In alcune chiamate API che prevedono argomenti variabili, useranno un puntatore NULL per indicare la fine dell'elenco degli argomenti. In questo caso, l'utilizzo di '0' anziché NULL può causare problemi. Su una piattaforma a 64 bit, la chiamata va_arg richiede un puntatore a 64 bit, ma passerai solo un numero intero a 32 bit. Mi sembra che tu faccia affidamento sugli altri 32 bit per essere azzerato per te? Ho visto alcuni compilatori (ad es. Icpc di Intel) che non sono così gentili - e questo ha comportato errori di runtime.


NULLforse non è portatile e non è sicuro. Potrebbero esserci ancora delle piattaforme #define NULL 0(secondo le FAQ di Stroustrup: dovrei usare NULL o 0? Citato dalla domanda principale ed è tra i primi risultati della ricerca). Almeno nel vecchio C ++, 0ha un significato concettuale speciale nel contesto del puntatore. Non dovresti pensare concretamente ai bit. Si noti inoltre che in diversi contesti interi ( short, int, long long) " sizeof(0)" sarà diverso. Penso che questa risposta sia un po 'fuorviante.
FooF

(Personalmente come programmatore C nella vita quotidiana, sono venuto a visitare questa domanda per capire il motivo per cui la gente vuole usare NULLal posto di (char *)0, (const char *)0o (struct Boo *)0o (void *)0o quant'altro per esprime l'intento più chiaramente -. Senza essere (a mio parere) troppo ingombrante)
foof

Vota. sta succedendo al compilatore C msvc2013. a 64 bit, 0 quando si converte in puntatore non è garantito che sia NULL Pointer.
pengMiao,

16

Se ricordo bene, NULL è definito in modo diverso nelle intestazioni che ho usato. Per C è definito come (void *) 0 e per C ++ è definito come 0. Il codice assomigliava a:

#ifndef __cplusplus
#define NULL (void*)0
#else
#define NULL 0
#endif

Personalmente uso ancora il valore NULL per rappresentare i puntatori null, rende esplicito che stai utilizzando un puntatore anziché un tipo integrale. Sì, internamente il valore NULL è ancora 0 ma non è rappresentato come tale.

Inoltre, non faccio affidamento sulla conversione automatica di numeri interi in valori booleani, ma li confronto esplicitamente.

Ad esempio preferisco usare:

if (pointer_value != NULL || integer_value == 0)

piuttosto che:

if (pointer_value || !integer_value)

Basti dire che questo è tutto risolto in C ++ 11 dove si può semplicemente usare al nullptrposto di NULL, e anche nullptr_tquesto è il tipo di a nullptr.


15

Direi che la storia ha parlato e coloro che hanno discusso a favore dell'uso di 0 (zero) hanno sbagliato (incluso Bjarne Stroustrup). Gli argomenti a favore di 0 erano principalmente l'estetica e la "preferenza personale".

Dopo la creazione di C ++ 11, con il suo nuovo tipo nullptr, alcuni compilatori hanno iniziato a lamentarsi (con parametri predefiniti) di passare 0 a funzioni con argomenti puntatore, perché 0 non è un puntatore.

Se il codice fosse stato scritto utilizzando NULL, una semplice ricerca e sostituzione avrebbe potuto essere eseguita tramite la base di codice per renderlo invece nullptr. Se sei bloccato con il codice scritto usando la scelta di 0 come puntatore, è molto più noioso aggiornarlo.

E se devi scrivere subito un nuovo codice nello standard C ++ 03 (e non puoi usare nullptr), dovresti semplicemente usare NULL. Ti renderà molto più semplice l'aggiornamento in futuro.


11

Di solito uso 0. Non mi piacciono le macro e non esiste alcuna garanzia che alcune intestazioni di terze parti che stai utilizzando non ridefiniscano NULL per essere qualcosa di strano.

È possibile utilizzare un oggetto nullptr come proposto da Scott Meyers e altri fino a quando C ++ non ottiene una parola chiave nullptr:

const // It is a const object...
class nullptr_t 
{
public:
    template<class T>
    operator T*() const // convertible to any type of null non-member pointer...
    { return 0; }

    template<class C, class T>
    operator T C::*() const   // or any type of null member pointer...
    { return 0; }

private:
    void operator&() const;  // Can't take address of nullptr

} nullptr = {};

Google "nullptr" per maggiori informazioni.


9
Qualsiasi libreria di terze parti che definisce NULL in modo diverso da 0 (o (void*)0se compilata come codice C) richiede solo problemi e non deve essere utilizzata.
Adam Rosenfield,

2
Hai mai visto una libreria che ridefinisce NULL? Mai? Se una tale libreria fosse mai esistita, avresti problemi più grandi del NULL ridefinito, come ad esempio che stai usando una libreria abbastanza stupida da ridefinire NULL.
Andy Lester,

1
Ben più di un decennio fa ricordo vagamente di aver avuto a che fare con alcune intestazioni di terze parti, probabilmente Orbix o ObjectStore, che definivano NULL. Penso di avere un odio patologico per le macro dopo aver sprecato diversi giorni e notti nel tentativo di far funzionare varie intestazioni di terze parti con Windows.
jon-hanson,

2
"Non mi piace le macro" è una strana critica di un oggetto #define. Forse vuoi dire che non ti piace il preprocessore C?
Andrew Prock,

@Andrew - L'unico vantaggio di NULLover (type *)0è la ricerca, mi sembra. Altrimenti sembra inutile offuscamento se non fosse un idioma in C. Personalmente penso che il linguaggio di diffusione in NULLtutto il luogo meriti di morire. NULLè inutile macro secondo me. Il rasoio di Occam ha del lavoro da fare qui ...
FooF

11

Una volta ho lavorato su una macchina in cui 0 era un indirizzo valido e NULL era definito come un valore ottale speciale. Su quella macchina (0! = NULL), quindi codice come

char *p;

...

if (p) { ... }

non funzionerebbe come previsto. DEVI scrivere

if (p != NULL) { ... }

Anche se credo che la maggior parte dei compilatori definisca NULL come 0 in questi giorni, ricordo ancora la lezione di quegli anni fa: NULL non è necessariamente 0.


26
Non stavi usando un compilatore conforme. Lo standard dice NULL è 0 e che il compilatore dovrebbe convertire 0 in un contesto di puntatore in un vero valore NULL vero per l'arch.
Evan Teran,

17
Si hai ragione. Questo avveniva a metà degli anni '80 prima che ANSI producesse uno standard C. Allora la conformità non esisteva e gli autori di compilatori erano liberi di interpretare il linguaggio come ritenevano opportuno. Ecco perché era necessario uno standard.
mxg

9

Penso che lo standard garantisca NULL == 0, quindi puoi fare entrambe le cose. Preferisco NULL perché documenta le tue intenzioni.


Se hai strutture nidificate, penso che dire foo.bar_ptr = (Bar *) 0esprima l'intento molto più chiaramente di foo.bar_ptr = NULL. Questa abitudine consente inoltre al compilatore di rilevare errori di errore. Per me, foo.bar_ptr = 0esprime l'intento oltre a usare NULLse so che foo.bar_ptrè un puntatore.
FooF

9

L'uso di 0 o NULL avrà lo stesso effetto.

Tuttavia, ciò non significa che siano entrambe buone pratiche di programmazione. Dato che non vi è alcuna differenza nelle prestazioni, la scelta di un'opzione di basso livello rispetto a un'alternativa agnostica / astratta è una cattiva pratica di programmazione. Aiuta i lettori del tuo codice a comprendere il tuo processo di pensiero .

NULL, 0, 0.0, '\ 0', 0x00 e quant'altro si traducono tutti nella stessa cosa, ma sono entità logiche diverse nel tuo programma. Dovrebbero essere usati come tali. NULL è un puntatore, 0 è quantità, 0x0 è un valore i cui bit sono interessanti, ecc. Non assegneresti '\ 0' a un puntatore, che sia compilato o meno.

So che alcune comunità incoraggiano a dimostrare una conoscenza approfondita di un ambiente rompendo i contratti ambientali. I programmatori responsabili, tuttavia, rendono il codice gestibile e mantengono tali pratiche fuori dal loro codice.


5

Strano, nessuno, incluso Stroustroup, l'ha menzionato. Mentre parliamo molto di standard ed estetica nessuno ha notato che è pericoloso da usare 0al NULLposto di, ad esempio, nella lista di argomenti variabili sull'architettura in cui sizeof(int) != sizeof(void*). Come Stroustroup, preferisco 0per ragioni estetiche, ma bisogna stare attenti a non usarlo dove il suo tipo potrebbe essere ambiguo.


E in quei luoghi pericolosi si può ancora utilizzare 0a condizione di specificare quale 0vuoi dire - per esempio (int *)0, (char *)0, (const char *)0o (void *)0oppure (unsigned long long) 0o qualsiasi altra cosa. Questo secondo me esprime l'intento molto più chiaro di NULL.
FooF

1
Certo, se non sai cosa NULLrappresenta.
Michael Krelin - hacker

Personalmente trovo un po 'sgradevole lanciare inutilmente qualcosa su (void *)quando potrei usare il tipo esatto. Ho dato di proposito un esempio di (tipicamente) numero intero a 64 bit nell'elenco perché è analogo al caso puntatore. Inoltre, se il mio ricordo più vecchio che C ++ definito NULLcome 0è accurata (si tratta di anni da quando ho programmato in C ++), allora si assiste alcun miglioramento nella correttezza del programma. Il più recente standard C ++ fornisce fortunatamente nullptrparole chiave, quindi possiamo sbarazzarci di questa NULLbruttezza e dell'intera controversia quando scriviamo C ++ più recenti.
FooF

Bene, ecco perché (void*)è stato sottratto al casting NULL. E in NULLrealtà esprime l'intento abbastanza chiaramente la maggior parte del tempo. E penso che il tuo ricordo sia sbagliato. Non sono sicuro degli standard, ma in pratica credo che sia stato (void*)0. E sì, nullptrè un buon prettificatore, anche se equivale alla stessa NULLcosa: specificare il puntatore null senza specificare il tipo.
Michael Krelin - hacker

1
@FooF, su alcune piattaforme - forse. Nella mia realtà ha funzionato e quindi sospetto che sia stato definito un puntatore. Per quanto riguarda la robustezza, sì, quello che stavo cercando di dire che usare nullptrporta lo stesso messaggio NULL, riguardava solo l'espressione dell'intenzione che hai menzionato all'inizio. (Preelaborazione NULLsu gccrendimenti moderni __null, qualunque essa sia).
Michael Krelin - hacker

4

Cerco di evitare l'intera domanda usando i riferimenti C ++ ove possibile. Piuttosto che

void foo(const Bar* pBar) { ... }

potresti spesso essere in grado di scrivere

void foo(const Bar& bar) { ... }

Certo, questo non funziona sempre; ma i puntatori null possono essere abusati.


3

Sono con Stroustrup su questo :-) Dato che NULL non fa parte del linguaggio, preferisco usare 0.


3

Per lo più preferenze personali, anche se si potrebbe argomentare che NULL rende abbastanza ovvio che l'oggetto è un puntatore che attualmente non punta a nulla, ad es.

void *ptr = &something;
/* lots o' code */
ptr = NULL; // more obvious that it's a pointer and not being used

IIRC, lo standard non richiede che NULL sia 0, quindi usare qualunque cosa sia definita in <stddef.h> è probabilmente il migliore per il tuo compilatore.

Un altro aspetto dell'argomento è se si debbano usare confronti logici (cast implicito da bool) o controllo esplicito contro NULL, ma ciò dipende anche dalla leggibilità.


3

Preferisco usare NULL in quanto chiarisce che il tuo intento è che il valore rappresenta un puntatore e non un valore aritmetico. Il fatto che sia una macro è sfortunato, ma dal momento che è così ampiamente radicato c'è poco pericolo (a meno che qualcuno non faccia qualcosa di veramente stordito). Vorrei che fosse una parola chiave dall'inizio, ma cosa puoi fare?

Detto questo, non ho alcun problema con l'utilizzo dei puntatori come valori di verità in se stessi. Proprio come con NULL, è un linguaggio radicato.

C ++ 09 aggiungerà il costrutto nullptr che penso sia atteso da tempo.


1

Uso sempre 0. Non per nessun motivo reale, solo perché quando stavo imparando il C ++ per la prima volta, ho letto qualcosa che mi raccomandava di usare 0 e l'ho sempre fatto così. In teoria potrebbe esserci un problema di confusione nella leggibilità, ma in pratica non ho mai incontrato un problema del genere in migliaia di ore-uomo e milioni di righe di codice. Come dice Stroustrup, è davvero solo un problema estetico personale fino a quando lo standard diventa nullptr.


1

Qualcuno mi ha detto una volta ... Ho intenzione di ridefinire NULL a 69. Da allora non lo uso: P

Rende il tuo codice abbastanza vulnerabile.

Modificare:

Non tutto nello standard è perfetto. La macro NULL è una costante puntatore null C ++ definita dall'implementazione non completamente compatibile con la macro C NULL, che oltre al tipo che nasconde implicito lo converte in uno strumento inutile e soggetto a errori.

NULL non si comporta come un puntatore null ma come un letterale O / OL.

Dimmi che il prossimo esempio non è confuso:

void foo(char *); 
void foo(int); 
foo(NULL); // calls int version instead of pointer version! 

È a causa di tutto ciò, nel nuovo standard appare std :: nullptr_t

Se non vuoi aspettare il nuovo standard e vuoi usare un nullptr, usa almeno uno decente come quello proposto da Meyers (vedi il commento di jon.h).


5
NULLè una parte ben definita dello standard C ++. Consentire alle persone a cui piace ridefinire le macro standard di modificare il codice nel progetto rende il codice "vulnerabile"; usare NULLno.
CB Bailey,

1

Beh, sostengo di non usare i puntatori 0 o NULL quando possibile.

Usarli prima o poi porterà a errori di segmentazione nel tuo codice. Nella mia esperienza questo, e gli indicatori in gereral sono una delle maggiori fonti di bug in C ++

inoltre, porta a istruzioni "if-not-null" in tutto il codice. Molto più bello se puoi contare sempre su uno stato valido.

C'è quasi sempre un'alternativa migliore.


2
Un errore di segmentazione garantito (ed è garantito sui sistemi moderni in caso di discrezione 0) è utile per il debug. Molto meglio che dereferenziare la spazzatura casuale e sapere chi ne risulta.
Razze di leggerezza in orbita,

-4

Impostare un puntatore su 0 non è poi così chiaro. Soprattutto se vieni in un linguaggio diverso dal C ++. Ciò include C e Javascript.

Recentemente ho eliminato un po 'di codice in questo modo:

virtual void DrawTo(BITMAP *buffer) =0;

per pura funzione virtuale per la prima volta. Ho pensato che fosse un po 'di jiberjash magico per una settimana. Quando mi sono reso conto che stava semplicemente impostando il puntatore di funzione su a null(dato che le funzioni virtuali sono solo puntatori di funzione nella maggior parte dei casi per C ++) mi sono cacciato da solo.

virtual void DrawTo(BITMAP *buffer) =null;

sarebbe stato meno confuso di quel bastione senza una corretta spaziatura ai miei nuovi occhi. In realtà, mi chiedo perché il C ++ non impieghi maiuscole e minuscole nullcome se ora impiegasse lettere minuscole false e true.


In generale preferisco NULl a 0 per i puntatori. Tuttavia "= 0;" è il modo idiomatico di dichiarare una funzione virtuale pura in C ++. Consiglio vivamente di non usare '= NULL;' per questo caso particolare.
danio,

Questo è il commento più divertente su StackOverflow. Probabilmente sai già ora che l'esempio che hai fornito è una sintassi per la pura funzione virtuale e non un puntatore. E sì, @danio ha ragione, non dovresti usare NULL per la pura funzione virtuale.
sgowd,
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.