Che cosa significa fare un "controllo null" in C o C ++?


21

Sto imparando il C ++ e sto facendo fatica a capire null. In particolare, i tutorial che ho letto menzionano fare un "controllo null", ma non sono sicuro di cosa significhi o perché sia ​​necessario.

  • Che cosa è esattamente null?
  • Che cosa significa "controllare null"?
  • Devo sempre verificare la presenza di null?

Qualsiasi esempio di codice sarebbe molto apprezzato.



Consiglierei di ottenere alcuni tutorial migliori, se tutti quelli che leggi parlano di controlli null senza mai spiegarli e fornire un codice di esempio ...
underscore_d

Risposte:


26

In C e C ++, i puntatori sono intrinsecamente non sicuri, vale a dire, quando si fa riferimento a un puntatore, è responsabilità dell'utente assicurarsi che punti in un punto valido; questo fa parte della "gestione manuale della memoria" (al contrario degli schemi di gestione automatica della memoria implementati in linguaggi come Java, PHP o .NET runtime, che non ti permetteranno di creare riferimenti non validi senza un notevole sforzo).

Una soluzione comune che rileva molti errori è impostare tutti i puntatori che non puntano a nulla come NULL(o, in C ++ corretto 0), e controllarli prima di accedere al puntatore. In particolare, è pratica comune inizializzare tutti i puntatori su NULL (a meno che non si abbia già qualcosa a cui indicarli quando li si dichiarano) e impostarli su NULL quando l'utente deleteo free()loro (a meno che non escano immediatamente dall'ambito di applicazione). Esempio (in C, ma anche C ++ valido):

void fill_foo(int* foo) {
    *foo = 23; // this will crash and burn if foo is NULL
}

Una versione migliore:

void fill_foo(int* foo) {
    if (!foo) { // this is the NULL check
        printf("This is wrong\n");
        return;
    }
    *foo = 23;
}

Senza il controllo null, passare un puntatore NULL in questa funzione causerà un segfault e non c'è nulla che tu possa fare: il sistema operativo semplicemente ucciderà il tuo processo e forse scaricherà il core o aprirà una finestra di dialogo di crash report. Con il controllo null in atto, è possibile eseguire la corretta gestione degli errori e ripristinare con garbo: correggere il problema da soli, interrompere l'operazione corrente, scrivere una voce di registro, informare l'utente, qualunque cosa sia appropriata.


3
@MrLister cosa vuoi dire, i controlli null non funzionano in C ++? Devi solo inizializzare il puntatore su null quando lo dichiari.
TZHX,

1
Quello che voglio dire è che devi ricordare di impostare il puntatore su NULL o non funzionerà. E se ricordi, in altre parole se sai che il puntatore è NULL, non avrai comunque bisogno di chiamare fill_foo. fill_foo controlla se il puntatore ha un valore, non se il puntatore ha un valore valido . In C ++, non è garantito che i puntatori siano NULL o che abbiano un valore valido.
Mr Lister,

4
Un assert () sarebbe una soluzione migliore qui. Non ha senso cercare di "essere al sicuro". Se NULL è stato inserito, è ovviamente sbagliato, quindi perché non semplicemente arrestarsi in modo esplicito per rendere il programmatore pienamente consapevole? (E in produzione, non importa, perché hai dimostrato che nessuno chiamerà fill_foo () con NULL, giusto? Davvero, non è poi così difficile.)
Ambroz Bizjak

7
Non dimenticare di menzionare che una versione ancora migliore di questa funzione dovrebbe utilizzare riferimenti anziché puntatori, rendendo obsoleto il controllo NULL.
Doc Brown,

4
Non si tratta della gestione manuale della memoria e anche un programma gestito esploderà (o solleverà almeno un'eccezione, proprio come farebbe un programma nativo nella maggior parte delle lingue) se si tenta di dedurre un riferimento nullo.
Mason Wheeler,

7

Le altre risposte hanno praticamente coperto la tua domanda esatta. Viene effettuato un controllo nullo per accertarsi che il puntatore ricevuto in realtà punti a un'istanza valida di un tipo (oggetti, primitive, ecc.).

Aggiungerò qui il mio consiglio. Evitare controlli null. :) I controlli nulli (e altre forme di programmazione difensiva) ingombrano il codice e lo rendono effettivamente più soggetto agli errori rispetto ad altre tecniche di gestione degli errori.

La mia tecnica preferita quando si tratta di puntatori a oggetti è quella di utilizzare il modello Null Object . Ciò significa restituire un (puntatore - o ancora meglio, riferimento a un array o elenco vuoto) anziché null, oppure restituire una stringa vuota ("") anziché null, o persino la stringa "0" (o qualcosa di equivalente a "niente "nel contesto) in cui ti aspetti che venga analizzato in un numero intero.

Come bonus, ecco qualcosa che potresti non sapere sul puntatore null, che fu (inizialmente formalmente) implementato da CAR Hoare per la lingua Algol W nel 1965.

Lo chiamo il mio errore da miliardi di dollari. Fu l'invenzione del riferimento nullo nel 1965. A quel tempo, stavo progettando il primo sistema di tipi completo per riferimenti in un linguaggio orientato agli oggetti (ALGOL W). Il mio obiettivo era quello di garantire che ogni uso dei riferimenti fosse assolutamente sicuro, con il controllo eseguito automaticamente dal compilatore. Ma non ho resistito alla tentazione di inserire un riferimento null, semplicemente perché era così facile da implementare. Ciò ha portato a innumerevoli errori, vulnerabilità e arresti anomali del sistema, che probabilmente hanno causato un miliardo di dollari di dolore e danni negli ultimi quarant'anni.


6
Null Object è anche peggio che avere un puntatore null. Se un algoritmo X richiede dati Y che non hai, allora questo è un bug nel tuo programma , che stai semplicemente nascondendo facendo finta di esserlo.
DeadMG

Dipende dal contesto e in entrambi i casi il test della "presenza di dati" supera il test null nel mio libro. Dalla mia esperienza, se un algoritmo funziona su, diciamo, un elenco e l'elenco è vuoto, allora l'algoritmo semplicemente non ha nulla a che fare, e lo fa semplicemente usando istruzioni di controllo standard come for / foreach.
Yam Marcovic,

Se l'algoritmo non ha nulla a che fare, allora perché lo chiami anche? E il motivo per cui potresti aver voluto chiamarlo in primo luogo è perché fa qualcosa di importante .
DeadMG

@DeadMG Poiché i programmi riguardano l'input e, nel mondo reale, a differenza dei compiti a casa, l'input può essere irrilevante (es. Vuoto). Il codice viene comunque chiamato in entrambi i modi. Sono disponibili due opzioni: o si controlla la pertinenza (o il vuoto) oppure si progettano i propri algoritmi in modo che leggano e funzionino bene senza verificare esplicitamente la pertinenza utilizzando le istruzioni condizionali.
Yam Marcovic,

Sono venuto qui per fare quasi lo stesso commento, quindi ti ho dato il mio voto. Tuttavia, aggiungerei anche che questo è rappresentativo di un problema più grande di oggetti zombi - ogni volta che hai oggetti con inizializzazione (o distruzione) a più stadi che non sono completamente vivi ma non del tutto morti. Quando vedi un codice "sicuro" in lingue senza finalizzazione deterministica che ha aggiunto controlli in ogni funzione per vedere se l'oggetto è stato eliminato, è questo problema generale che ne eleva la testa. Non dovresti mai if-null, dovresti lavorare con stati che hanno gli oggetti di cui hanno bisogno per la loro vita.
ex0du5,

4

Il valore del puntatore null rappresenta un "nulla" ben definito; è un valore di puntatore non valido che garantisce un confronto diverso da qualsiasi altro valore di puntatore. Tentare di dereferenziare un puntatore nullo comporta un comportamento indefinito e di solito porta a un errore di runtime, quindi si desidera assicurarsi che un puntatore non sia NULL prima di provare a dereferenziarlo. Numerose funzioni di libreria C e C ++ restituiranno un puntatore null per indicare una condizione di errore. Ad esempio, la funzione di libreria mallocrestituirà un valore di puntatore nullo se non è in grado di allocare il numero di byte richiesti e il tentativo di accedere alla memoria tramite quel puntatore (di solito) porterà a un errore di runtime:

int *p = malloc(sizeof *p * N);
p[0] = ...; // this will (usually) blow up if malloc returned NULL

Quindi dobbiamo assicurarci che la mallocchiamata abbia avuto successo controllando il valore di pNULL:

int *p = malloc(sizeof *p * N);
if (p != NULL) // or just if (p)
  p[0] = ...;

Ora, tieniti stretto le calze un minuto, questo diventerà un po 'accidentato.

C'è un valore di puntatore null e una costante di puntatore null e i due non sono necessariamente gli stessi. Il valore del puntatore null è qualunque valore l'architettura sottostante usi per rappresentare "da nessuna parte". Questo valore può essere 0x00000000 o 0xFFFFFFFF o 0xDEADBEEF o qualcosa di completamente diverso. Non dare per scontato che il valore del puntatore null sia sempre 0.

La costante puntatore null , OTOH, è sempre un'espressione integrale con valore 0. Per quanto riguarda il codice sorgente , 0 (o qualsiasi espressione integrale che restituisce 0) rappresenta un puntatore nullo. Sia C che C ++ definiscono la macro NULL come costante puntatore null. Quando viene compilato il codice, la costante puntatore null verrà sostituita con il valore puntatore null appropriato nel codice macchina generato.

Inoltre, tenere presente che NULL è solo uno dei molti possibili valori di puntatore non validi ; se si dichiara una variabile del puntatore automatico senza inizializzarla esplicitamente, ad esempio

int *p;

il valore inizialmente memorizzato nella variabile è indeterminato e potrebbe non corrispondere a un indirizzo di memoria valido o accessibile. Sfortunatamente, non esiste un modo (portatile) per dire se un valore di puntatore non NULL è valido o meno prima di tentare di usarlo. Quindi, se hai a che fare con i puntatori, di solito è una buona idea inizializzarli esplicitamente su NULL quando li dichiari e impostarli su NULL quando non puntano attivamente a nulla.

Si noti che questo è più un problema in C che C ++; il C ++ idiomatico non dovrebbe usare così tanto i puntatori.


3

Ci sono un paio di metodi, tutti essenzialmente fanno la stessa cosa.

int * foo = NULL; // a volte impostato su 0x00 o 0 o 0L invece di NULL

controllo null (verificare se il puntatore è null), versione A

if (foo == NULL)

controllo null, versione B

if (! foo) // poiché NULL è definito come 0,! foo restituirà un valore da un puntatore null

controllo null, versione C

if (foo == 0)

Dei tre, preferisco usare il primo controllo in quanto dice esplicitamente ai futuri sviluppatori che cosa stavi cercando di controllare E chiarisce che ti aspettavi che Foo fosse un puntatore.


2

Non L'unico motivo per utilizzare un puntatore in C ++ è perché si desidera esplicitamente la presenza di puntatori null; altrimenti, puoi prendere un riferimento, che è sia semanticamente più facile da usare che garantisce non nullo.


1
@James: 'nuovo' in modalità kernel?
Nemanja Trifunovic,

1
@James: un'implementazione di C ++ che rappresenta le funzionalità di cui gode la maggior parte dei programmatori C ++. Ciò include tutte le funzionalità del linguaggio C ++ 03 (eccetto export) e tutte le funzionalità della libreria C ++ 03 e TR1 e una buona porzione di C ++ 11.
DeadMG

5
Io faccio desiderio della gente non dire che "i riferimenti garantiscono non nullo." Non lo fanno. È facile generare un riferimento null come un puntatore null e si propagano allo stesso modo.
mjfgates,

2
@Stargazer: la domanda è ridondante al 100% quando si utilizzano gli strumenti come suggeriscono i progettisti del linguaggio e le buone pratiche.
DeadMG

2
@DeadMG, non importa se è ridondante. Non hai risposto alla domanda . Lo dirò di nuovo: -1.
riwalk

-1

Se non si controlla il valore NULL, in particolare, se si tratta di un puntatore a una struttura, è possibile che si sia verificata una vulnerabilità di sicurezza: la dereferenza del puntatore NULL. La dereferenza del puntatore NULL può portare a alcune altre gravi vulnerabilità della sicurezza come overflow del buffer, condizioni di competizione ... che possono consentire agli aggressori di assumere il controllo del tuo computer.

Molti produttori di software come Microsoft, Oracle, Adobe, Apple ... rilasciano patch software per correggere queste vulnerabilità di sicurezza. Penso che dovresti controllare il valore NULL di ogni puntatore :)

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.