Verifica del puntatore NULL in C / C ++ [chiuso]


153

In una recente revisione del codice, un collaboratore sta cercando di imporre che tutti i NULLcontrolli sui puntatori vengano eseguiti nel modo seguente:

int * some_ptr;
// ...
if (some_ptr == NULL)
{
    // Handle null-pointer error
}
else
{
    // Proceed
}

invece di

int * some_ptr;
// ...
if (some_ptr)
{
    // Proceed
}
else
{
    // Handle null-pointer error
}

Sono d'accordo che il suo modo sia un po 'più chiaro nel senso che sta esplicitamente dicendo "Assicurati che questo puntatore non sia NULL", ma lo contrasterei dicendo che chiunque sta lavorando su questo codice capirà che l'uso di una variabile puntatore in un ifL'istruzione sta implicitamente controllando NULL. Inoltre penso che il secondo metodo abbia una minore possibilità di introdurre un bug delk:

if (some_ptr = NULL)

che è solo un dolore assoluto da trovare ed eseguire il debug.

In che modo preferisci e perché?


32
Avere uno stile coerente a volte è più importante di quello che scegli.
Mark Ransom,

15
@Mark: la coerenza è sempre il fattore più importante del tuo stile.
sbi,

13
"Inoltre ritengo che il secondo metodo abbia una minore possibilità di introdurre un bug delk: if (some_ptr = NULL)" Ecco perché alcune persone usano if (NULL == some_ptr), non è possibile assegnare a NULL in modo da ottenere un errore di compilazione.
user122302

14
la maggior parte dei compilatori in questi giorni mette in guardia sull'assegnazione in condizionali - sfortunatamente troppi sviluppatori ignorano le avvertenze del compilatore.
Mike Ellery,

10
Non sono d'accordo con l'affermazione sopra che la coerenza è ciò che conta. Questo non ha nulla a che fare con la coerenza; non il buon tipo comunque. Questa è l'area in cui dovremmo lasciare che i programmatori esprimano il loro stile. Se c'è una persona che non capisce immediatamente nessuno dei due moduli, dovrebbe interrompere immediatamente la lettura del codice e prendere in considerazione un cambiamento di carriera. Dirò di più: se c'è una consistenza cosmetica che non puoi applicare automaticamente con uno script, rimuovilo. Il costo di drenaggio morale dell'uomo è MODO più importante di tali decisioni stupide le persone fanno in un incontro.
Wilhelmtell,

Risposte:


203

Nella mia esperienza, sono preferiti i test del modulo if (ptr)o if (!ptr). Non dipendono dalla definizione del simbolo NULL. Non espongono l'opportunità per l'assegnazione accidentale. E sono chiari e concisi.

Modifica: Come sottolinea SoapBox in un commento, sono compatibili con le classi C ++ come ad esempio auto_ptroggetti che fungono da puntatori e che forniscono una conversione boolper abilitare esattamente questo idioma. Per questi oggetti, un confronto esplicito NULLdovrebbe invocare una conversione in puntatore che potrebbe avere altri effetti collaterali semantici o essere più costoso del semplice controllo dell'esistenza che la boolconversione implica.

Ho una preferenza per il codice che dice cosa significa senza testo non necessario. if (ptr != NULL)ha lo stesso significato di if (ptr)ma a scapito della specificità ridondante. La prossima cosa logica è scrivere if ((ptr != NULL) == TRUE)e in questo modo risiede la follia. Il linguaggio C è chiaro che un valore booleano testato da if, whileo simili ha una specifica significato di valore diverso da zero è vero e lo zero è falso. La ridondanza non lo rende più chiaro.


23
Inoltre, sono compatibili con le classi wrapper di puntatore (shared_ptr, auto_ptr, scoped_tr, ecc.) Che in genere sovrascrivono l'operatore bool (o safe_bool).
SoapBox,

2
Sto votando questa come "risposta" semplicemente a causa del riferimento a auto_ptr che si aggiunge alla discussione. Dopo aver scritto la domanda, è chiaro che questo è in realtà più soggettivo di qualsiasi altra cosa e probabilmente non è adatto per una "risposta" di StackOver poiché nessuno di questi potrebbe essere "corretto" a seconda delle circostanze.
Bryan Marble,

2
@Bryan, lo rispetto, e se arriva una risposta "migliore", sentiti libero di spostare il segno di spunta secondo necessità. Per quanto riguarda la soggettività, sì, è soggettiva. Ma è almeno una discussione soggettiva in cui vi sono questioni oggettive che non sono sempre evidenti quando viene posta la domanda. La linea è sfocata. E soggettivo ... ;-)
RBerteig

1
Una cosa importante da notare: fino a poco tempo fa, ero a favore di scrivere "if (ptr)" invece di "if (ptr! = NULL)" o "if (ptr! = Nullptr)" in quanto è più conciso e quindi rende il codice più leggibile. Ma ho appena scoperto che il confronto genera (resp.) Avviso / errore per il confronto con NULL / nullptr sui compilatori recenti. Quindi, se vuoi controllare un puntatore, meglio fare "if (ptr! = NULL)" invece di "if (ptr)". Se si dichiara erroneamente ptr come int, il primo controllo genererà un avviso / errore, mentre quest'ultimo verrà compilato senza alcun avviso.
Arnaud,

1
@David 天宇 Wong No, non è quello che intendevo. L'esempio in quella pagina è la sequenza di due righe in int y = *x; if (!x) {...}cui l'ottimizzatore è autorizzato a ragionare che dato che *xsarebbe indefinito se xè NULL, non deve essere NULL e quindi !xdeve essere FALSE. L'espressione non!ptr è un comportamento indefinito. Nell'esempio, se il test fosse stato eseguito prima di qualsiasi utilizzo , l'ottimizzatore avrebbe sbagliato a eliminare il test. *x
RBerteig,

52

if (foo)è abbastanza chiaro. Usalo


25

Inizierò con questo: la coerenza è il re, la decisione è meno importante della coerenza nella tua base di codice.

In C ++

NULL è definito come 0 o 0L in C ++.

Se hai letto Il linguaggio di programmazione C ++ Bjarne Stroustrup suggerisce di usare 0esplicitamente per evitare la NULLmacro durante l'assegnazione , non sono sicuro che abbia fatto lo stesso con i confronti, è passato un po 'di tempo da quando ho letto il libro, penso che abbia appena fatto if(some_ptr)senza un confronto esplicito, ma sono confuso su questo.

La ragione di ciò è che la NULLmacro è ingannevole (come quasi tutte le macro) in realtà è 0letterale, non un tipo unico come suggerisce il nome. Evitare le macro è una delle linee guida generali in C ++. D'altra parte, 0sembra un numero intero e non lo è se confrontato o assegnato ai puntatori. Personalmente potrei andare in entrambi i modi, ma in genere ignoro il confronto esplicito (anche se ad alcune persone questo non piace, questo è probabilmente il motivo per cui hai un collaboratore che suggerisce comunque un cambiamento).

Indipendentemente dai sentimenti personali, questa è in gran parte una scelta del minimo male in quanto non esiste un metodo giusto.

Questo è chiaro e un linguaggio comune e lo preferisco, non c'è possibilità di assegnare accidentalmente un valore durante il confronto e si legge chiaramente:

if(some_ptr){;}

Questo è chiaro se sai che some_ptrè un tipo di puntatore, ma può anche apparire come un confronto intero:

if(some_ptr != 0){;}

Questo è chiaro, nei casi comuni ha senso ... Ma è un'astrazione che perde, NULLè in realtà 0letterale e potrebbe finire per essere abusato facilmente:

if(some_ptr != NULL){;}

C ++ 0x ha nullptr che ora è il metodo preferito in quanto è esplicito e preciso, basta fare attenzione all'assegnazione accidentale:

if(some_ptr != nullptr){;}

Fino a quando non sarai in grado di migrare a C ++ 0x, direi che è una perdita di tempo preoccuparsi di quale di questi metodi usi, sono tutti insufficienti ed è per questo che è stato inventato nullptr (insieme a problemi di programmazione generici che sono emersi con un perfetto inoltro .) La cosa più importante è mantenere la coerenza.

In C.

C è una bestia diversa.

In C NULL può essere definito come 0 o come ((void *) 0), C99 consente costanti puntatore null definite dall'implementazione. Quindi, in realtà, si riduce alla definizione di NULL dell'implementazione e dovrai ispezionarla nella tua libreria standard.

Le macro sono molto comuni e in generale vengono utilizzate molto per compensare le carenze nel supporto della programmazione generica nel linguaggio e in altre cose. Il linguaggio è molto più semplice e la dipendenza dal pre-processore è più comune.

Da questa prospettiva probabilmente consiglierei di usare la NULLdefinizione macro in C.


tl; dr e anche se hai ragione su 0in C ++, ha un significato in C: (void *)0. 0è preferibile NULLin C ++, perché gli errori di tipo possono essere una PITA.
Matt Joiner,

@Matt: Ma NULLè zero comunque.
GManNickG

@GMan: Non sul mio sistema: #define NULL ((void *)0)dalinux/stddef.h
Matt Joiner

@Matt: in C ++. Hai detto che 0 è preferibile, ma NULLdeve essere zero.
GManNickG

Questo è un buon punto, ho trascurato di menzionarlo e sto migliorando la mia risposta suggerendola. ANSI C può avere NULL definito come ((void *) 0), C ++ definisce NULL come 0. Non ho analizzato direttamente lo standard per questo, ma la mia comprensione è che in C ++ NULL può essere 0 o 0L.
M2tM,

19

Lo uso if (ptr), ma non vale assolutamente la pena discuterne.

Mi piace la mia strada perché è concisa, anche se altri dicono che == NULLrende più facile la lettura e più esplicita. Vedo da dove vengono, non sono d'accordo che le cose extra lo rendono più facile. (Odio la macro, quindi sono di parte.) A te.

Non sono d'accordo con la tua tesi. Se non ricevi avvisi per le assegnazioni in modo condizionale, devi aumentare i livelli di avviso. Semplice come quella. (E per l'amore di tutto ciò che è buono, non cambiarli.)

Nota in C ++ 0x, possiamo fare if (ptr == nullptr), che per me fa più bello di lettura. (Ancora una volta, odio la macro. Ma nullptrè carino.) Lo faccio if (ptr)comunque, solo perché è quello a cui sono abituato.


spero che 5 anni di esperienza in più cambino idea :) se C ++ / C avesse un operatore simile a if_exist (), avrebbe senso.
magulla,

10

Francamente, non vedo perché sia ​​importante. Uno dei due è abbastanza chiaro e chiunque abbia una moderata esperienza con C o C ++ dovrebbe capire entrambi. Un commento, però:

Se si prevede di riconoscere l'errore e non si continua a eseguire la funzione (ovvero, si genererà un'eccezione o si restituirà immediatamente un codice di errore), è necessario renderlo una clausola di protezione:

int f(void* p)
{
    if (!p) { return -1; }

    // p is not null
    return 0;
}

In questo modo, eviti il ​​"codice freccia".


qualcuno ha indicato qui kukuruku.co/hub/programming/i-do-not-know-c che if(!p)è un comportamento indefinito. Potrebbe funzionare o no.
David 天宇 Wong,

@David 天宇 Wong se stai parlando dell'esempio n. 2 su quel link, if(!p)non è il comportamento indefinito, è la linea prima di esso. E if(p==NULL)avrebbe lo stesso identico problema.
Mark Ransom

8

Personalmente l'ho sempre usato if (ptr == NULL)perché rende esplicito il mio intento, ma a questo punto è solo un'abitudine.

L'utilizzo =al posto di ==verrà rilevato da qualsiasi compilatore competente con le impostazioni di avviso corrette.

Il punto importante è scegliere uno stile coerente per il tuo gruppo e attenersi ad esso. Indipendentemente dal modo in cui vai, alla fine ti abituerai e la perdita di attrito quando lavorerai nel codice di altre persone sarà il benvenuto.


7

Solo un altro punto a favore della foo == NULLpratica: se fooè, diciamo, un int *o un bool *, allora il if (foo)controllo può essere accidentalmente interpretato da un lettore come test del valore della punta, cioè come if (*foo). Il NULLconfronto qui è un promemoria che stiamo parlando di un puntatore.

Ma suppongo che una buona convenzione di denominazione metta in discussione questo argomento.


4

In realtà, utilizzo entrambe le varianti.

Esistono situazioni in cui si verifica prima la validità di un puntatore e, se è NULL, si ritorna / esci da una funzione. (So ​​che questo può portare alla discussione "una funzione dovrebbe avere solo un punto di uscita")

Il più delle volte, si controlla il puntatore, quindi si fa quello che si desidera e quindi si risolve il caso di errore. Il risultato può essere il brutto codice rientrato x-times con più if.


1
Personalmente odio il codice freccia che risulterebbe utilizzando un punto di uscita. Perché non uscire se conosco già la risposta?
ruslik,

1
@ruslik: in C (o C con classi di classe C ++), avere più ritorni rende più difficile la pulizia. Nel C ++ "reale" questo è ovviamente un problema perché la pulizia è gestita da RAII, ma alcuni programmatori sono (per motivi più o meno validi) bloccati in passato e rifiutano o non possono fare affidamento su RAII .
jalf

@jalf in questi casi gotopuò essere molto utile. Anche in molti casi un malloc()che avrebbe bisogno di pulizia potrebbe essere sostituito da un più veloce alloca(). So che non sono raccomandati, ma esistono per un motivo (secondo me, un'etichetta gotoben nota per è molto più pulita di un if/elsealbero offuscato ).
ruslik,

1
allocaè non standard e completamente non sicuro. Se fallisce, il tuo programma esplode. Non vi è alcuna possibilità di recupero e poiché lo stack è relativamente piccolo, è probabile che si verifichino errori. In caso di fallimento, è possibile ostruire l'heap e introdurre vulnerabilità che compromettono i privilegi. Non usare mai allocao vlas a meno che tu non abbia un piccolo limite sulla dimensione che verrà allocata (e quindi potresti anche usare un array normale).
R .. GitHub FERMA AIUTANDO ICE

4

Il linguaggio di programmazione C (K&R) ti farebbe controllare null == ptr per evitare un'assegnazione accidentale.


23
K&R avrebbe anche chiesto "cos'è un puntatore intelligente?" Le cose stanno cambiando nel mondo C ++.
Alex Emelianov,

2
o meglio, le cose sono già cambiate nel mondo C ++ ";)
jalf

3
Dove lo afferma K&R (ref)? Non usano mai questo stile da soli. Nel capitolo 2 di K & R2 lo raccomandano anche if (!valid)sopra if (valid == 0).
schot

6
Accomodati, gente. Ha detto k & r, non K&R. Sono ovviamente meno famosi in quanto le loro iniziali non sono nemmeno in maiuscolo.
jmucchiello,

1
capitalizzare ti rallenta
Derek,

3

Se lo stile e il formato faranno parte delle tue recensioni, ci dovrebbe essere una guida di stile concordata su cui misurare. Se ce n'è uno, fai quello che dice la guida di stile. Se non ce n'è uno, dettagli come questo dovrebbero essere lasciati mentre sono scritti. È una perdita di tempo ed energia e distrae da ciò che le recensioni di codice dovrebbero davvero scoprire. Scherzi a parte, senza una guida di stile spingerei a NON modificare il codice in questo modo per principio, anche quando non usa la convenzione che preferisco.

E non è importante, ma la mia preferenza personale è if (ptr). Il significato per me è più immediatamente evidente che persino if (ptr == NULL).

Forse sta cercando di dire che è meglio gestire le condizioni di errore prima del percorso felice? In tal caso, non sono ancora d'accordo con il revisore. Non so che ci sia una convenzione accettata per questo, ma secondo me la condizione più "normale" dovrebbe venire prima in ogni affermazione if. In questo modo ho meno scavi da fare per capire di cosa tratta la funzione e come funziona.

L'eccezione a ciò è se l'errore mi fa uscire dalla funzione, o posso recuperare da essa prima di andare avanti. In questi casi, gestisco prima l'errore:

if (error_condition)
  bail_or_fix();
  return if not fixed;

// If I'm still here, I'm on the happy path

Affrontando in anticipo l'insolita condizione, posso occuparmene e poi dimenticarmene. Ma se non riesco a tornare sul percorso felice gestendolo in anticipo, allora dovrebbe essere gestito dopo il caso principale perché rende il codice più comprensibile. Secondo me.

Ma se non è in una guida di stile, allora è solo la mia opinione e la tua opinione è altrettanto valida. Standardizzare o no. Non lasciare che un revisore pseudo-standardizzi solo perché ha un'opinione.


1

Questo è uno dei fondamenti di entrambi i linguaggi che i puntatori valutano su un tipo e un valore che possono essere usati come espressione di controllo, boolin C ++ e intin C. Basta usarlo.


1
  • I puntatori non sono booleani
  • I compilatori C / C ++ moderni emettono un avviso quando si scrive if (foo = bar)per caso.

Quindi preferisco

if (foo == NULL)
{
    // null case
}
else
{
    // non null case
}

o

if (foo != NULL)
{
    // non null case
}
else
{
    // null case
}

Tuttavia, se stessi scrivendo una serie di linee guida di stile, non inserirò cose come questa, inserirò cose come:

Assicurati di fare un controllo nullo sul puntatore.


2
È vero che i puntatori non sono booleani, ma in C le istruzioni if ​​non prendono i booleani: prendono espressioni intere.
Ken,

@Ken: questo perché C è rotto in questo senso. Concettualmente, è un'espressione booleana e (secondo me) dovrebbe essere trattata come tale.
JeremyP

1
Alcune lingue hanno istruzioni if ​​che verificano solo null / non-null. Alcune lingue hanno istruzioni if ​​che verificano solo un numero intero per segno (un test a 3 vie). Non vedo alcun motivo per considerare C "rotto" perché hanno scelto un concetto diverso che ti piace. Ci sono molte cose che odio di C, ma ciò significa che il modello del programma C non è lo stesso del mio modello mentale, né che uno di noi (io o C) sia rotto.
Ken,

@Ken: un booleano non è un numero o un puntatore concettualmente , non importa quale lingua.
JeremyP,

1
Non ho detto che un booleano fosse un numero o un puntatore. Ho detto che non c'è motivo di insistere sul fatto che un'istruzione if dovrebbe o potrebbe solo prendere un'espressione booleana e ha offerto controesempi, oltre a quello a portata di mano. Molte lingue prendono qualcosa di diverso da un booleano, e non solo nel modo "zero / diverso da zero" di C. Nell'informatica, avere un'istruzione if che accetta (solo) un valore booleano è uno sviluppo relativamente recente.
Ken,

1

Sono un grande fan del fatto che C / C ++ non verifica i tipi nelle condizioni booleane a if, fore whilele dichiarazioni. Uso sempre quanto segue:

if (ptr)

if (!ptr)

anche su numeri interi o altri tipi che vengono convertiti in bool:

while(i--)
{
    // Something to do i times
}

while(cin >> a >> b)
{
    // Do something while you've input
}

La codifica in questo stile è più leggibile e più chiara per me. Solo la mia opinione personale.

Di recente, mentre lavoravo con il microcontrollore OKI 431, ho notato che:

unsigned char chx;

if (chx) // ...

è più efficiente di

if (chx == 1) // ...

perché in un secondo momento il compilatore deve confrontare il valore di chx con 1. Dove chx è solo un flag vero / falso.


1

La maggior parte dei compilatori che ho usato avvertirà almeno ifdell'incarico senza ulteriore sintassi dello zucchero, quindi non compro quell'argomento. Detto questo, ho usato entrambi in modo professionale e non ho preferenze per nessuno dei due. Il == NULLè sicuramente più chiara anche se a mio parere.

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.