Che cosa significa eseguire un “ controllo null ” in C o C ++?

Sto imparando il C ++ e ho difficoltà a capire il null. In particolare, i tutorial che ho letto menzionano lesecuzione di un “controllo nullo”, ma non sono sicuro di cosa significhi o perché sia necessario.

  • Cosè esattamente nullo?
  • Cosa significa “verificare la presenza di null”?
  • Devo sempre verificare la presenza di null?

Qualsiasi esempio di codice sarebbe molto apprezzato.

Commenti

Risposta

In C e C ++, i puntatori sono intrinsecamente pericolosi, ovvero quando si dereferenzia un puntatore, è tua responsabilità assicurarti che punti a qualcosa di valido; questo fa parte di ciò che riguarda la “gestione manuale della memoria” (al contrario degli schemi di gestione automatica della memoria implementati in linguaggi come Java , PHP o il runtime .NET, che non ti consentirà di creare riferimenti non validi senza uno sforzo considerevole).

Una soluzione comune che rileva molti errori è impostare tutti i puntatori che non puntano a nulla come NULL (o, nel C ++ corretto, 0) e verificandola prima di accedere al puntatore. In particolare, è pratica comune inizializzare tutti i puntatori su NULL (a meno che tu non abbia già qualcosa a cui indirizzarli quando li dichiari) e impostarli su NULL quando delete o free() (a meno che non escano dallambito immediatamente dopo). 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 a questa funzione causerà un segfault e non cè niente che tu possa fare – il sistema operativo semplicemente terminerà il tuo processo e forse core-dump o farà apparire una finestra di dialogo di segnalazione di crash. Con il controllo null in atto, è possibile eseguire una corretta gestione degli errori e ripristinare con garbo: correggere il problema da soli, interrompere loperazione corrente, scrivere una voce di registro, avvisare lutente, qualunque cosa sia appropriata.

Commenti

  • @MrLister cosa intendi, controlli null non ‘ t funziona in C ++? Devi solo inizializzare il puntatore su null quando lo dichiari.
  • Quello che voglio dire è che devi ricordarti di impostare il puntatore su NULL o ha vinto ‘ t lavoro. E se ricordi, in altre parole, se sai che il puntatore è NULL, ‘ non avrai bisogno di chiamare fill_foo comunque. 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 abbiano un valore valido.
  • Un assert () sarebbe una soluzione migliore qui. ‘ non ha senso cercare di ” essere al sicuro “. Se è stato passato NULL, ‘ è ovviamente sbagliato, quindi perché non bloccarsi 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 è così difficile.)
  • Non ‘ non dimenticare per menzionare che una versione ancora migliore di questa funzione dovrebbe usare riferimenti invece di puntatori, rendendo il controllo NULL obsoleto.
  • Non è questo largomento della gestione manuale della memoria, e anche un programma gestito salterà fuori, ( o solleva almeno uneccezione, proprio come farà un programma nativo nella maggior parte delle lingue) se provi a dereferenziare un riferimento nullo.

Risposta

Le altre risposte coprivano praticamente la tua domanda esatta. Viene effettuato un controllo nullo per essere sicuri che il puntatore ricevuto punti effettivamente a unistanza valida di un tipo (oggetti, primitive, ecc.).

Aggiungerò qui il mio consiglio, tuttavia. Evita i controlli nulli. 🙂 I controlli nulli (e altre forme di programmazione difensiva) ingombrano il codice e lo rendono effettivamente più soggetto a errori rispetto ad altre tecniche di gestione degli errori.

La mia tecnica preferita quando si tratta di puntatori a oggetti consiste nellutilizzare il pattern oggetto nullo . Ciò significa restituire un (puntatore o, meglio ancora, riferimento a un) array o elenco vuoto invece di null, oppure restituendo una stringa vuota (“”) invece di null, o anche la stringa “0” (o qualcosa di equivalente a “niente” nel contesto) dove ti aspetti che venga analizzata come numero intero.

Come bonus, ecco “qualcosa che potresti non sapere sul puntatore nullo, che è stato (prima formalmente) implementato da CAR Hoare per il linguaggio Algol W nel 1965.

Lo chiamo il mio errore da un miliardo di dollari. Era linvenzione del riferimento nullo nel 1965. A quel tempo, stavo progettando il primo sistema di tipi completo per i riferimenti in un oggetto linguaggio orientato (ALGOL W). Il mio obiettivo era di garantire che tutti gli usi dei riferimenti dovessero essere assolutamente sicuri, con il controllo eseguito automaticamente dal compilatore. Ma non ho potuto resistere alla tentazione di inserire un riferimento nullo, semplicemente perché era così facile da implementare. Ciò ha portato a innumerevoli errori, vulnerabilità e arresti anomali del sistema, che hanno probabilmente causato un miliardo di dollari di dolore e danni negli ultimi quarantanni.

Commenti

  • Oggetto null è anche peggio di avere solo un puntatore nullo. Se un algoritmo X richiede dati Y che non hai, allora si tratta di un bug nel tuo programma , che stai semplicemente nascondendo fingendo di sì.
  • Dipende da il contesto e in entrambi i casi il test per ” presenza di dati ” batte il test per null nel mio libro. In base alla mia esperienza, se un algoritmo funziona, ad esempio, su un elenco e lelenco è vuoto, lalgoritmo semplicemente non ha nulla a che fare e lo fa semplicemente utilizzando istruzioni di controllo standard come for / foreach.
  • Se lalgoritmo non ha nulla a che fare, perché lo chiami? E il motivo per cui avresti potuto chiamarlo in primo luogo è perché fa qualcosa di importante .
  • @DeadMG Perché i programmi riguardano linput e nel mondo reale, a differenza compiti a casa, linput può essere irrilevante (es. vuoto). Il codice viene comunque chiamato in entrambi i casi. Hai due opzioni: o controlli la pertinenza (o il vuoto) o progetti i tuoi algoritmi in modo che leggano e funzionino bene senza verificare esplicitamente la pertinenza utilizzando dichiarazioni condizionali.
  • Sono venuto qui per fare quasi il stesso commento, quindi ti ho dato il mio voto. Tuttavia, aggiungerei anche che questo è rappresentativo di un problema più grande degli oggetti zombie – ogni volta che hai oggetti con inizializzazione (o distruzione) a più stadi che non sono completamente vivi ma non del tutto morti. Quando vedi il codice ” safe ” nelle lingue senza finalizzazione deterministica che ha aggiunto controlli in ogni funzione per vedere se loggetto è stato eliminato, è questo problema generale che si solleva la testa della ‘. Non dovresti mai if-null, dovresti lavorare con stati che hanno gli oggetti di cui hanno bisogno per la loro vita.

Answer

Il valore del puntatore nullo rappresenta un “da nessuna parte” ben definito; è un valore del puntatore non valido di cui è garantito un confronto diverso da qualsiasi altro valore del puntatore. Il tentativo di dereferenziare un puntatore nullo risulta in un comportamento indefinito e di solito porta a un errore di runtime, quindi è necessario assicurarsi che un puntatore non sia NULL prima di tentare di dereferenziarlo. Un certo numero di funzioni di libreria C e C ++ restituirà un puntatore nullo per indicare una condizione di errore. Ad esempio, la funzione di libreria malloc restituirà un valore di puntatore nullo se non può allocare il numero di byte che sono stati richiesti, e il tentativo di accedere alla memoria tramite quel puntatore porterà (di solito) 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 chiamata malloc sia riuscita controllando il valore di p contro NULL:

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

Ora, aggrappati ai tuoi calzini un minuto, questo otterrà un un po irregolare.

Cè un puntatore nullo valore e un puntatore nullo costante , e i due non sono necessariamente la stessa cosa. Il puntatore nullo valore è qualsiasi valore utilizzato dallarchitettura sottostante per rappresentare “da nessuna parte”. Questo valore può essere 0x00000000, 0xFFFFFFFF o 0xDEADBEEF o qualcosa di completamente diverso. Non dare per scontato che il puntatore nullo valore sia sempre 0.

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

Inoltre, tieni presente che NULL è solo uno dei tanti possibili valori di puntatore non validi ; se dichiari una variabile del puntatore automatico senza inizializzarla esplicitamente, come

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 (portabile) 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 in NULL quando li dichiari e per impostarli su NULL quando “non puntano attivamente a nulla.

Nota che questo è più un problema in C che in C ++; Il C ++ idiomatico non dovrebbe “t usare più di tanto i puntatori.

Risposta

Ci sono un paio di metodi, tutti essenzialmente fanno lo stesso cosa.

 int *foo = NULL; //sometimes set to 0x00 or 0 or 0L instead of NULL 

controllo nullo (controlla se il puntatore è nullo), versione A

 if( foo == NULL) 

null check, versione B

 if( !foo ) //since NULL is defined as 0, !foo will return a value from a null pointer 

null check, versione C

 if( foo == 0 ) 

Dei tre, preferisco usare il primo controllo poiché dice esplicitamente agli sviluppatori futuri cosa stavi cercando di controllare E rende chiaro che ti aspettavi che pippo fosse un puntatore.

Risposta

Non “t. Lunico motivo per utilizzare un puntatore in C ++ è perché si desidera esplicitamente la presenza di puntatori nulli; altrimenti, puoi prendere un riferimento, che è semanticamente più facile da usare e garantisce un valore non nullo.

Commenti

  • @James: ‘ new ‘ in modalità kernel?
  • @James: unimplementazione di C ++ che rappresenta le capacità di una maggioranza significativa di C ++ i programmatori si divertono. Ciò include tutte le funzionalità del linguaggio C ++ 03 (tranne export) e tutte le funzionalità della libreria C ++ 03 e TR1 e una buona parte di C ++ 11.
  • vorrei che le persone non ‘ dicessero che ” i riferimenti garantiscono un valore non nullo. ” Non ‘ t. È facile generare un riferimento nullo come un puntatore nullo e si propagano allo stesso modo.
  • @Stargazer: la domanda è ridondante al 100% quando usi gli strumenti nel modo in cui i progettisti del linguaggio e bravi la pratica suggerisce che dovresti.
  • @DeadMG, non ‘ importa se è ridondante. Non hai ‘ risposto alla domanda . ‘ lo ripeto: -1.

Risposta

Se non controlli il valore NULL, specialmente se si tratta di un puntatore a una struttura, potresti incontrare una vulnerabilità di sicurezza: la dereferenziazione del puntatore NULL. La dereferenziazione del puntatore NULL può portare ad altre gravi vulnerabilità di sicurezza come loverflow del race condition … che può consentire a un utente malintenzionato di prendere il controllo del tuo computer.

Molti fornitori di software come Microsoft, Oracle, Adobe, Apple … rilasciano patch software per correggere queste vulnerabilità di sicurezza. Penso che dovresti controlla il valore NULL di ogni puntatore 🙂

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *