Perché l'indirizzo zero viene utilizzato per il puntatore nullo?


121

In C (o C ++ per quella materia), i puntatori sono speciali se hanno valore zero: mi consiglia di impostare i puntatori a zero dopo aver liberato la memoria, perché significa che liberare nuovamente il puntatore non è pericoloso; quando chiamo malloc restituisce un puntatore con valore zero se non riesce a recuperarmi la memoria; Uso if (p != 0)tutto il tempo per assicurarmi che i puntatori passati siano validi, ecc.

Ma poiché l'indirizzamento della memoria inizia da 0, 0 non è forse un indirizzo valido come un altro? Come può essere utilizzato 0 per gestire i puntatori nulli se è così? Perché invece un numero negativo non è nullo?


Modificare:

Un mucchio di buone risposte. Riassumerò ciò che è stato detto nelle risposte espresse come la mia mente lo interpreta e spero che la comunità mi correggerà se fraintendevo.

  • Come ogni altra cosa nella programmazione, è un'astrazione. Solo una costante, non propriamente correlata all'indirizzo 0. C ++ 0x lo sottolinea aggiungendo la parola chiave nullptr.

  • Non è nemmeno un'astrazione di indirizzo, è la costante specificata dallo standard C e il compilatore può tradurla in un altro numero purché si assicuri che non sia mai uguale a un indirizzo "reale" e sia uguale ad altri puntatori nulli se 0 non è il miglior valore da utilizzare per la piattaforma.

  • Nel caso in cui non si tratti di un'astrazione, come nei primi tempi, l'indirizzo 0 è utilizzato dal sistema e off limits per il programmatore.

  • Il mio suggerimento sul numero negativo è stato un po 'di folle brainstorming, lo ammetto. L'uso di un intero con segno per gli indirizzi è un po 'dispendioso se significa che a parte il puntatore nullo (-1 o qualsiasi altra cosa) lo spazio dei valori è diviso equamente tra interi positivi che rendono indirizzi validi e numeri negativi che vengono semplicemente sprecati.

  • Se qualsiasi numero è sempre rappresentabile da un tipo di dato, è 0. (Probabilmente anche 1 lo è. Penso all'intero a un bit che sarebbe 0 o 1 se non firmato, o solo il bit con segno se firmato, o il numero intero a due bit che sarebbe [-2, 1]. Ma allora potresti semplicemente scegliere che 0 sia nullo e 1 sia l'unico byte accessibile in memoria.)

C'è ancora qualcosa che è irrisolto nella mia mente. La domanda Stack Overflow Puntatore a un indirizzo fisso specifico mi dice che anche se 0 per il puntatore nullo è un'astrazione, gli altri valori del puntatore non sono necessariamente. Questo mi porta a pubblicare un'altra domanda di Stack Overflow, potrei mai voler accedere all'indirizzo zero? .


11
Potresti facilmente cambiare if (p != 0)a if (p)quale è un idioma comune in C e C ++, anche se dovrai uscire dall'abitudine se prendi Java.
Paul Tomblin

14
Eliminare qualcosa due volte significa che il tuo codice è sbagliato. Sconsiglio di impostare i puntatori su null dopo in modo da poter bloccare e risolvere il problema, non sopprimerlo. In ogni caso, si commette l'errore di presumere che un indirizzo sia un numero intero. Questo non è necessariamente vero e 0 rappresenta semplicemente un valore del puntatore effettivo che è specifico dell'implementazione. Un "indirizzo negativo" non ha senso, concettualmente.
GManNickG

6
@GMan: Forse è anche una buona idea impostare il puntatore su un indirizzo che forzerà un arresto anomalo, come 0xDEADBEEF.
Billy ONeal

5
La domanda che non morirà mai!

8
@ Noah: il punto è impostato su null -> nascondi errori di programmazione, non impostare su null -> trova errori di programmazione. Non so voi, ma vorrei che il mio codice fosse corretto.
GManNickG

Risposte:


65

2 punti:

  • solo il valore costante 0 nel codice sorgente è il puntatore nullo: l'implementazione del compilatore può utilizzare qualsiasi valore desideri o necessita nel codice in esecuzione. Alcune piattaforme hanno un valore di puntatore speciale "non valido" che l'implementazione potrebbe utilizzare come puntatore nullo. La C FAQ ha una domanda: "Scherzi a parte, qualche macchina reale ha davvero usato puntatori nulli diversi da zero o rappresentazioni diverse per puntatori a tipi diversi?" , che indica diverse piattaforme che hanno utilizzato questa proprietà di 0 come puntatore nullo nella sorgente C mentre rappresentata in modo diverso in fase di esecuzione. Lo standard C ++ ha una nota che chiarisce che la conversione di "un'espressione di costante integrale con valore zero produce sempre un puntatore nullo,

  • un valore negativo potrebbe essere utilizzabile dalla piattaforma tanto quanto un indirizzo: lo standard C doveva semplicemente scegliere qualcosa da usare per indicare un puntatore nullo e zero è stato scelto. Onestamente non sono sicuro che siano stati presi in considerazione altri valori sentinella.

Gli unici requisiti per un puntatore nullo sono:

  • è garantito un confronto diverso da un puntatore a un oggetto reale
  • ogni due puntatori nulli compareranno uguali (C ++ lo perfeziona in modo tale che questo debba essere mantenuto solo per i puntatori allo stesso tipo)

12
+1 Sospetto che 0 sia stato scelto solo per ragioni storiche. (0 è un indirizzo di partenza e non valido, la maggior parte delle volte.) Ovviamente in generale una tale ipotesi non è sempre vera, ma 0 funziona abbastanza bene.
GManNickG

8
Anche lo spazio potrebbe essere stato un fattore determinante. Ai tempi in cui C fu sviluppato per la prima volta, la memoria era MOLTO più costosa di adesso. Il numero zero può essere convenientemente calcolato utilizzando un'istruzione XOR o senza la necessità di caricare un valore immediato. A seconda dell'architettura, questo potrebbe potenzialmente risparmiare spazio.
Sparky

6
@GMan - Hai ragione. Sulle prime CPU, l'indirizzo di memoria zero era speciale e aveva una protezione hardware contro l'accesso da parte del software in esecuzione (in alcuni casi era l'inizio del vettore di ripristino e la sua modifica poteva impedire il ripristino o l'avvio della CPU). I programmatori hanno utilizzato questa protezione hardware come una forma di rilevamento degli errori nel loro software, consentendo alla logica di decodifica dell'indirizzo della CPU di verificare la presenza di puntatori non inizializzati o non validi invece di dover spendere istruzioni della CPU per farlo. La convenzione rimane fino ad oggi, anche se lo scopo dell'indirizzo zero potrebbe essere cambiato.
bta

10
Il compilatore Minix a 16 bit utilizzava 0xFFFF per NULL.
Joshua

3
In molti sistemi embedded, 0 è un indirizzo valido. Anche il valore -1 (tutti i bit uno) è un indirizzo valido. I checksum per le ROM sono difficili da calcolare quando i dati iniziano all'indirizzo 0. :-(
Thomas Matthews

31

Storicamente, lo spazio degli indirizzi che inizia da 0 era sempre ROM, utilizzato per alcuni sistemi operativi o routine di gestione degli interrupt di basso livello, al giorno d'oggi, poiché tutto è virtuale (compreso lo spazio degli indirizzi), il sistema operativo può mappare qualsiasi allocazione a qualsiasi indirizzo, quindi può nello specifico NON allocare nulla all'indirizzo 0.


6
Questo è praticamente tutto. È per convenzione storica, ei primi indirizzi sono stati usati per i gestori di interrupt, quindi sono inutilizzabili per i normali programmi. Inoltre, 0 è "vuoto", che può essere interpretato come nessun valore / nessun puntatore.
TomTom

15

IIRC, non è garantito che il valore "puntatore nullo" sia zero. Il compilatore traduce 0 in qualsiasi valore "nullo" appropriato per il sistema (che in pratica è probabilmente sempre zero, ma non necessariamente). La stessa traduzione viene applicata ogni volta che si confronta un puntatore con lo zero. Poiché puoi confrontare i puntatori solo l'uno con l'altro e con questo valore speciale 0, esso isola il programmatore dal sapere qualsiasi cosa sulla rappresentazione della memoria del sistema. Per quanto riguarda il motivo per cui hanno scelto 0 invece di 42 o qualcosa del genere, immagino sia perché la maggior parte dei programmatori inizia a contare da 0 :) (Inoltre, sulla maggior parte dei sistemi 0 è il primo indirizzo di memoria e volevano che fosse conveniente, poiché in fare pratica con le traduzioni come sto descrivendo raramente avvengono effettivamente; la lingua le consente solo).


5
@ Justin: hai frainteso. La costante 0 è sempre il puntatore nullo. Ciò che @meador sta dicendo è che è possibile che il puntatore nullo (indicato dalla costante 0) non corrisponda all'indirizzo zero. Su alcune piattaforme, la creazione di un puntatore nullo ( int* p = 0) potrebbe creare un puntatore contenente il valore 0xdeadbeefo qualsiasi altro valore che preferisce. 0 è un puntatore nullo, ma un puntatore nullo non è necessariamente un puntatore all'indirizzo zero. :)
jalf

Un puntatore NULL è un valore riservato e, a seconda del compilatore, potrebbe essere qualsiasi modello di bit. Il puntatore NULL non significa che punti all'indirizzo 0.
Sharjeel Aziz

3
Ma @Jalf, la costante 0 non è sempre il puntatore nullo. È ciò che scriviamo quando vogliamo che il compilatore compili per noi l' effettivo puntatore nullo della piattaforma . In pratica, il puntatore nullo di solito fa corrispondere all'indirizzo pari a zero, però, e io interpretare la domanda di Joel come chiedere perché. Si suppone che ci sia un byte di memoria valido a quell'indirizzo, dopotutto, quindi perché non usare un indirizzo inesistente di un byte inesistente invece di rimuovere un byte valido dal gioco? (Sto scrivendo quello che immagino stesse pensando Joel, non una domanda che mi sto facendo.)
Rob Kennedy,

@ Rob: una specie di. So cosa intendi e hai ragione, ma lo sono anch'io. :) Il numero intero costante 0 rappresenta il puntatore nullo a livello di codice sorgente. Il confronto di un puntatore null con 0 restituisce true. Assegnare 0 a un puntatore imposta quel puntatore su null. 0 è il puntatore nullo. Ma l'effettiva rappresentazione in memoria di un puntatore nullo potrebbe essere diversa dallo schema di bit zero. (Ad ogni modo, il mio commento era in risposta al commento di @ Justin ora cancellato, non alla domanda di @ Joel. :)
jalf

@jalf @Rob Hai bisogno di alcuni termini per chiarire, credo. :) Da §4.10 / 1: "Una costante puntatore nullo è un'espressione costante integrale rvalue di tipo intero che restituisce zero. Una costante puntatore nullo può essere convertita in un tipo puntatore; il risultato è il valore puntatore nullo di quel tipo e è distinguibile da ogni altro valore di puntatore a oggetto o puntatore a tipo di funzione. "
GManNickG

15

Devi fraintendere il significato della costante zero nel contesto del puntatore.

Né in C né in C ++ i puntatori possono "avere valore zero". I puntatori non sono oggetti aritmetici. Non possono avere valori numerici come "zero" o "negativo" o qualcosa di simile. Quindi la tua affermazione su "i puntatori ... hanno valore zero" semplicemente non ha senso.

In C e C ++ i puntatori possono avere il valore del puntatore nullo riservato . La rappresentazione effettiva del valore del puntatore nullo non ha nulla a che fare con gli "zeri". Può essere assolutamente qualsiasi cosa appropriata per una data piattaforma. È vero che sulla maggior parte delle piattaforme il valore del puntatore nullo è rappresentato fisicamente da un valore dell'indirizzo zero effettivo. Tuttavia, se su qualche piattaforma l'indirizzo 0 viene effettivamente utilizzato per qualche scopo (cioè potrebbe essere necessario creare oggetti all'indirizzo 0), il valore del puntatore nullo su tale piattaforma sarà molto probabilmente diverso. Potrebbe essere rappresentato fisicamente come 0xFFFFFFFFvalore indirizzo o come 0xBAADBAADvalore indirizzo, ad esempio.

Tuttavia, indipendentemente da come il valore del puntatore nullo viene rappresentato su una data piattaforma, nel codice continuerai comunque a designare i puntatori nulli per costante 0. Per assegnare un valore di puntatore nullo a un determinato puntatore, continuerai a utilizzare espressioni come p = 0. È responsabilità del compilatore realizzare ciò che si desidera e tradurlo nella corretta rappresentazione del valore del puntatore nullo, ovvero tradurlo nel codice che inserirà il valore dell'indirizzo 0xFFFFFFFFnel puntatore p, ad esempio.

In breve, il fatto che tu usi 0nel tuo codice sorce per generare valori di puntatore nullo non significa che il valore del puntatore nullo sia in qualche modo legato all'indirizzo 0. Quello 0che usate nel vostro codice sorgente è solo "zucchero sintattico" che non ha assolutamente alcuna relazione con l'effettivo indirizzo fisico a cui "punta" il valore del puntatore nullo.


3
<quote> I puntatori non sono oggetti aritmetici </quote> L'aritmetica dei puntatori è abbastanza ben definita in C e C ++. Parte del requisito è che entrambi i puntatori puntino all'interno dello stesso composto. Il puntatore nullo non punta a nessun composto, quindi utilizzarlo nelle espressioni aritmetiche del puntatore è illegale. Ad esempio, non è garantito (p1 - nullptr) - (p2 - nullptr) == (p1 - p2).
Ben Voigt

5
@ Ben Voigt: la specifica del linguaggio definisce la nozione di tipo aritmetico . Sto solo dicendo che i tipi di puntatore non appartengono alla categoria dei tipi aritmetici. L'aritmetica dei puntatori è una storia diversa e completamente non correlata, una mera coincidenza linguistica.
AnT

1
Come si suppone che qualcuno che legge oggetti aritmetici sappia che significa "nel senso di tipi aritmetici" e non "nel senso di operatori aritmetici" (molti dei quali sono utilizzabili su puntatori) o "nel senso di aritmetica dei puntatori". Per quanto riguarda le coincidenze linguistiche, l'oggetto aritmetico ha più lettere in comune con l' aritmetica dei puntatori rispetto ai tipi aritmetici . Allo stesso tempo, lo standard parla del valore del puntatore . Il poster originale probabilmente significava la rappresentazione intera di un puntatore piuttosto che il valore del puntatore , e NULLnon ha bisogno di essere rappresentato esplicitamente da 0.
Ben Voigt

Bene, per esempio il termine oggetti scalari nella terminologia C / C ++ è solo un'abbreviazione per oggetti di tipi scalari (proprio come oggetti POD = oggetti di tipi POD ). Ho usato il termine oggetti aritmetici esattamente allo stesso modo, che significa oggetti di tipi aritmetici . Mi aspetto che "qualcuno" lo capisca in questo modo. Qualcuno che non lo fa può sempre chiedere un chiarimento.
AnT

1
ho lavorato su un sistema in cui (per quanto riguarda l'hardware) null era 0xffffffff e 0 era un indirizzo perfettamente valido
pm100

8

Ma poiché l'indirizzamento della memoria inizia da 0, 0 non è forse un indirizzo valido come un altro?

Su alcuni / molti / tutti i sistemi operativi, l'indirizzo di memoria 0 è in qualche modo speciale. Ad esempio, è spesso mappato su memoria non valida / inesistente, il che causa un'eccezione se si tenta di accedervi.

Perché invece un numero negativo non è nullo?

Penso che i valori del puntatore siano tipicamente trattati come numeri senza segno: altrimenti ad esempio un puntatore a 32 bit sarebbe in grado di indirizzare solo 2 GB di memoria, invece di 4 GB.


4
Ho codificato su un dispositivo in cui l'indirizzo zero era un indirizzo valido e non c'era protezione della memoria. Anche i puntatori nulli erano tutti a bit zero; se hai scritto accidentalmente su un puntatore nullo, allora hai saltato le impostazioni del sistema operativo che erano all'indirizzo zero; l'ilarità di solito non ne derivava.
MM

1
Sì: su una CPU x86 in modalità non protetta, ad esempio, l'indirizzo 0 è la tabella vettoriale degli interrupt .
ChrisW

@ChrisW: su x86 in modalità non protetta, l'indirizzo zero in particolare è il vettore di interrupt divisione per zero, che alcuni programmi potrebbero avere ragioni del tutto legittime per la scrittura.
supercat

Anche su piattaforme in cui l'archiviazione utilizzabile inizierebbe dall'indirizzo fisico, zero, un'implementazione C potrebbe facilmente utilizzare l'indirizzo zero per contenere un oggetto il cui indirizzo non viene mai preso, oppure semplicemente lasciare inutilizzata la prima parola di memoria. Sulla maggior parte delle piattaforme, il confronto con zero salva un'istruzione rispetto al confronto con qualsiasi altra cosa, quindi anche sprecare la prima parola di archiviazione sarebbe più economico rispetto all'utilizzo di un indirizzo diverso da zero per null. Si noti che non è richiesto che gli indirizzi delle cose non coperte dallo standard C (ad esempio porte I / O o vettori di interrupt) siano diversi da null, né che ...
supercat

... il processo di sistema null-pointer accede in modo diverso da qualsiasi altro, quindi all-bits-zero è generalmente un buon indirizzo per "null" anche su sistemi in cui gli accessi alla posizione fisica zero sarebbero utili e significativi.
supercat

5

La mia ipotesi sarebbe che il valore magico 0 sia stato scelto per definire un puntatore non valido poiché potrebbe essere testato con meno istruzioni. Alcuni linguaggi macchina impostano automaticamente i flag di zero e segno in base ai dati durante il caricamento dei registri, in modo da poter testare un puntatore nullo con un semplice caricamento e quindi diramare le istruzioni senza eseguire un'istruzione di confronto separata.

(La maggior parte degli ISA imposta solo flag sulle istruzioni ALU, non carica, però. E di solito non stai producendo puntatori tramite calcoli, tranne nel compilatore quando analizzi il sorgente C. Ma almeno non hai bisogno di una costante arbitraria di larghezza del puntatore per confronta con.)

Sul Commodore Pet, Vic20 e C64, che erano le prime macchine su cui ho lavorato, la RAM partiva dalla posizione 0, quindi era totalmente valido leggere e scrivere usando un puntatore nullo se lo volevi davvero.


3

Penso che sia solo una convenzione. Deve essere presente un valore per contrassegnare un puntatore non valido.

Perdi solo un byte di spazio degli indirizzi, che raramente dovrebbe essere un problema.

Non ci sono indicatori negativi. I puntatori sono sempre senza segno. Inoltre, se potessero essere negativi, la tua convenzione significherebbe che perdi metà dello spazio degli indirizzi.


Nota: in realtà non si perde lo spazio degli indirizzi; è possibile ottenere un puntatore a indirizzo 0 facendo: char *p = (char *)1; --p;. Poiché il comportamento su un puntatore nullo non è definito dallo standard, questo sistema può peffettivamente leggere e scrivere l'indirizzo 0, incrementare per fornire l'indirizzo 1, ecc.
MM

@ MattMcNabb: Un'implementazione in cui l'indirizzo zero è un indirizzo hardware valido può definire perfettamente il comportamento di char x = ((char*)0);leggere l'indirizzo zero e memorizzare quel valore in x. Tale codice produrrebbe un comportamento indefinito su qualsiasi implementazione che non ne definisse il comportamento, ma il fatto che uno standard dica che qualcosa è comportamento indefinito non impedisce in alcun modo alle implementazioni di offrire le proprie specifiche per ciò che farà.
supercat

@supercat ITYM *(char *)0. Questo è vero, ma nel mio suggerimento l'implementazione non ha bisogno di definire il comportamento di *(char *)0o di qualsiasi altra operazione di puntatore nullo.
MM

1
@MattMcNabb: Il comportamento di char *p = (char*)1; --p;sarebbe definito dallo standard solo se quella sequenza fosse stata eseguita dopo che un puntatore a qualcosa di diverso dal primo byte di un oggetto era stato lanciato su un intptr_t, e il risultato di quel cast ha dato il valore 1 , e in quel caso particolare il risultato di --prestituirebbe un puntatore al byte che precede quello il cui valore del puntatore, quando è stato eseguito il cast intptr_t, ha prodotto 1.
supercat

3

Sebbene C utilizzi 0 per rappresentare il puntatore nullo, tieni presente che il valore del puntatore stesso potrebbe non essere uno zero. Tuttavia, la maggior parte dei programmatori utilizzerà solo sistemi in cui il puntatore nullo è, infatti, 0.

Ma perché zero? Bene, è un indirizzo condiviso da ogni sistema. E spesso gli indirizzi bassi sono riservati agli scopi del sistema operativo, quindi il valore funziona bene essendo off-limits per i programmi applicativi. L'assegnazione accidentale di un valore intero a un puntatore è probabile che finisca zero come qualsiasi altra cosa.


3
La ragione più probabile alla base di tutto ciò è che: è economico distribuire la memoria che è pre-inizializzata a zero e conveniente che i valori in quella memoria rappresentino qualcosa di significativo come il numero intero 0, virgola mobile 0.0 e puntatori nulli. I dati statici in C inizializzati su zero / null non devono occupare spazio nell'eseguibile e vengono mappati su un blocco riempito con zero quando vengono caricati. Lo zero può ricevere un trattamento speciale anche nei linguaggi macchina: facili confronti zero come "ramo se uguale a zero", ecc. MIPS ha anche un registro fittizio che è solo una costante zero.
Kaz

2

Storicamente, la scarsa memoria di un'applicazione era occupata dalle risorse di sistema. Fu in quei giorni che zero divenne il valore nullo di default.

Anche se questo non è necessariamente vero per i sistemi moderni, è comunque una cattiva idea impostare i valori del puntatore su qualcosa che non sia quello che l'allocazione di memoria ti ha dato.


2

Riguardo all'argomento di non impostare un puntatore su null dopo averlo eliminato in modo che il futuro elimini "esporre errori" ...

Se sei davvero, davvero preoccupato per questo, un approccio migliore, garantito per funzionare, è sfruttare assert ():


...
assert(ptr && "You're deleting this pointer twice, look for a bug?");
delete ptr;
ptr = 0;
...

Ciò richiede un po 'di digitazione extra e un controllo extra durante le build di debug, ma è certo che ti darà quello che vuoi: nota quando ptr viene cancellato "due volte". L'alternativa fornita nella discussione dei commenti, non impostare il puntatore su null in modo da ottenere un arresto anomalo, semplicemente non è garantito per avere successo. Peggio ancora, a differenza di quanto sopra, può causare un crash (o molto peggio!) Su un utente se uno di questi "bug" arriva allo scaffale. Infine, questa versione ti consente di continuare a eseguire il programma per vedere cosa succede effettivamente.

Mi rendo conto che questo non risponde alla domanda posta, ma ero preoccupato che qualcuno leggendo i commenti potesse giungere alla conclusione che è considerata una `` buona pratica '' NON impostare i puntatori a 0 se è possibile che vengano inviati a free () o eliminare due volte. In quei pochi casi in cui è possibile, non è MAI una buona pratica utilizzare Undefined Behavior come strumento di debug. Nessuno che abbia mai dovuto scovare un bug che alla fine è stato causato dall'eliminazione di un puntatore non valido lo proporrebbe. Questi tipi di errori richiedono ore per essere individuati e quasi sempre influenzano il programma in un modo totalmente inaspettato che è difficile o impossibile risalire al problema originale.


2

Un motivo importante per cui molti sistemi operativi utilizzano tutti i bit zero per la rappresentazione del puntatore nullo, è che questo significa memset(struct_with_pointers, 0, sizeof struct_with_pointers)e simili imposteranno tutti i puntatori all'interno struct_with_pointersdi puntatori nulli. Questo non è garantito dallo standard C, ma molti, molti programmi lo presumono.


1

In una delle vecchie macchine DEC (PDP-8, credo), il runtime C proteggeva dalla memoria la prima pagina di memoria in modo che qualsiasi tentativo di accedere alla memoria in quel blocco causasse il sollevamento di un'eccezione.


Il PDP-8 non aveva un compilatore C. Il PDP-11 non aveva protezione della memoria e il VAX era famoso per il ritorno silenzioso da 0 a dereferenze del puntatore NULL. Non sono sicuro a quale macchina si riferisca.
fuz

1

La scelta del valore sentinella è arbitraria, e questo viene infatti affrontato dalla prossima versione di C ++ (informalmente nota come "C ++ 0x", molto probabilmente conosciuta in futuro come ISO C ++ 2011) con l'introduzione del parola chiave nullptrper rappresentare un puntatore con valore nullo. In C ++, un valore 0 può essere usato come espressione di inizializzazione per qualsiasi POD e per qualsiasi oggetto con un costruttore predefinito, e ha il significato speciale di assegnare il valore sentinel nel caso di un'inizializzazione del puntatore. Per quanto riguarda il motivo per cui non è stato scelto un valore negativo, gli indirizzi di solito vanno da 0 a 2 N.-1 per alcuni valori N. In altre parole, gli indirizzi vengono generalmente trattati come valori senza segno. Se il valore massimo fosse usato come valore sentinella, allora dovrebbe variare da sistema a sistema a seconda della dimensione della memoria mentre 0 è sempre un indirizzo rappresentabile. Viene anche utilizzato per ragioni storiche, poiché l'indirizzo di memoria 0 era tipicamente inutilizzabile nei programmi e oggigiorno la maggior parte dei sistemi operativi ha parti del kernel caricate nelle pagine inferiori della memoria, e tali pagine sono tipicamente protette in modo tale che se toccato (dereferenziato) da un programma (salva il kernel) causerà un errore.


1

Deve avere un certo valore. Ovviamente non vuoi calpestare valori che l'utente potrebbe legittimamente voler utilizzare. Vorrei ipotizzare che poiché il runtime C fornisce il segmento BSS per i dati inizializzati zero, ha un certo senso interpretare zero come un valore del puntatore non inizializzato.


0

Raramente un sistema operativo ti consente di scrivere all'indirizzo 0. È comune tenere contenuti specifici del sistema operativo in una memoria insufficiente; vale a dire, IDT, tabelle delle pagine, ecc. (Le tabelle devono essere nella RAM, ed è più facile incollarle in fondo che cercare di determinare dove si trova la parte superiore della RAM). E nessun sistema operativo sano di mente te lo permetterà modificare le tabelle di sistema volenti o nolenti.

Questo potrebbe non essere stato nelle menti di K & R quando hanno creato C, ma (insieme al fatto che 0 == null è abbastanza facile da ricordare) rende 0 una scelta popolare.


Questo non è vero in modalità protetta e infatti, su alcune configurazioni Linux, puoi scrivere all'indirizzo virtuale 0.
L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳

0

Il valore 0è un valore speciale che assume vari significati in espressioni specifiche. Nel caso dei puntatori, come è stato sottolineato più volte, è usato probabilmente perché all'epoca era il modo più conveniente per dire "inserisci qui il valore predefinito della sentinella". Come espressione costante, non ha lo stesso significato di zero bit per bit (cioè, tutti i bit impostati su zero) nel contesto di un'espressione puntatore. In C ++, esistono diversi tipi che non hanno una rappresentazione zero bit per bit di NULLcome membro del puntatore e puntatore alla funzione del membro.

Fortunatamente, C ++ 0x ha una nuova parola chiave per "l'espressione che indica un puntatore non valido noto che non la mappa anche per bit a bit zero per le espressioni integrali": nullptr. Sebbene ci siano alcuni sistemi che puoi scegliere come target con C ++ che consentono la dereferenziazione dell'indirizzo 0 senza barfing, quindi fai attenzione al programmatore.


0

Ci sono già molte buone risposte in questo thread; ci sono probabilmente molte ragioni diverse per preferire il valore 0per i puntatori nulli, ma ne aggiungerò altri due:

  • In C ++, l'inizializzazione zero di un puntatore lo imposterà su null.
  • Su molti processori è più efficiente impostare un valore a 0 o testarlo uguale / non uguale a 0 rispetto a qualsiasi altra costante.

0

Ciò dipende dall'implementazione dei puntatori in C / C ++. Non esiste una ragione specifica per cui NULL è equivalente nelle assegnazioni a un puntatore.


-1

Ci sono ragioni storiche per questo, ma ci sono anche ragioni di ottimizzazione.

È comune che il sistema operativo fornisca un processo con pagine di memoria inizializzate a 0. Se un programma vuole interpretare parte di quella pagina di memoria come un puntatore, allora è 0, quindi è abbastanza facile per il programma determinare che quel puntatore è non inizializzato. (questo non funziona così bene se applicato a pagine flash non inizializzate)

Un altro motivo è che su molti processori è molto molto facile testare l'equivalenza di un valore a 0. A volte è un confronto gratuito fatto senza alcuna istruzione aggiuntiva necessaria e di solito può essere fatto senza la necessità di fornire un valore zero in un altro registro o come valore letterale nel flusso di istruzioni da confrontare.

I confronti economici per la maggior parte dei processori sono il segno minore di 0 e uguale a 0. (il segno maggiore di 0 e diverso da 0 sono impliciti da entrambi)

Poiché 1 valore su tutti i valori possibili deve essere riservato come cattivo o non inizializzato, potresti anche renderlo quello che ha il test più economico per l'equivalenza con il valore cattivo. Questo vale anche per le stringhe di caratteri terminate "\ 0".

Se dovessi provare a usare maggiore o minore di 0 per questo scopo, finiresti per dimezzare il tuo intervallo di indirizzi.


-2

La costante 0è usato al posto di NULLcausa C è stata fatta da alcuni bilioni uomini delle caverne di anni fa, NULL, NIL, ZIP, o NADDAavrebbe reso tutto molto più senso 0.

Ma poiché l'indirizzamento della memoria inizia da 0, 0 non è forse un indirizzo valido come un altro?

Infatti. Sebbene molti sistemi operativi non consentano di mappare qualsiasi cosa all'indirizzo zero, anche in uno spazio di indirizzamento virtuale (le persone si sono rese conto che C è un linguaggio insicuro e, riflettendo che i bug di dereferenziazione del puntatore nullo sono molto comuni, hanno deciso di "risolverli" rifiutando il codice dello spazio utente da mappare alla pagina 0; quindi, se chiami un callback ma il puntatore di callback è NULL, non finirai per eseguire del codice arbitrario).

Come può essere utilizzato 0 per gestire i puntatori nulli se è così?

Perché 0usato rispetto a un puntatore verrà sostituito con un valore specifico dell'implementazione , che è il valore di ritorno di malloc in caso di errore di malloc.

Perché invece un numero negativo non è nullo?

Questo sarebbe ancora più confuso.


Il tuo punto di vista sugli "uomini delle caverne", ecc. Probabilmente sta alla radice di esso, anche se penso che le specifiche siano diverse. Le prime forme di ciò che si è evoluto in C sono state progettate per funzionare su una particolare architettura in cui an intnon solo aveva le stesse dimensioni di un puntatore, ma in molti contesti an inte un puntatore potevano essere usati in modo intercambiabile. Se una routine si aspettava un puntatore e ne veniva passato uno come numero intero 57, la routine usava l'indirizzo con lo stesso schema di bit del numero 57. Su quelle macchine particolari, lo schema di bit per indicare un puntatore nullo era 0, quindi passare un int 0 passerebbe un puntatore nullo.
supercat

Da quel momento, il C si è evoluto in modo da poter essere utilizzato per scrivere programmi per una grande varietà di altre macchine con diverse rappresentazioni di numeri e puntatori. Mentre le costanti numeriche diverse da zero erano usate raramente come puntatori, gli zeri numerici costanti erano ampiamente usati per rappresentare i puntatori nulli. Disabilitare tale utilizzo avrebbe infranto il codice esistente, quindi ci si aspettava che i compilatori traducessero uno zero numerico in qualsiasi cosa l'implementazione utilizzi per rappresentare un puntatore nullo.
supercat

-4

( Si prega di leggere questo paragrafo prima di leggere il post. Chiedo a chiunque sia interessato a leggere questo post di provare a leggerlo attentamente e, naturalmente, di non votarlo meno finché non lo capisci completamente, grazie.)

Ora è un wiki della comunità, in quanto tale se qualcuno non è d'accordo con uno qualsiasi dei concetti, per favore modificalo, con una spiegazione chiara e dettagliata di cosa è sbagliato e perché, e se possibile per favore cita fonti o fornisci prove che possono essere riprodotte.

Risposta

Ecco alcuni altri motivi che potrebbero essere i fattori alla base di NULL == 0

  1. Il fatto che zero sia falso, quindi si può fare direttamente if(!my_ptr)invece di if(my_ptr==NULL).
  2. Il fatto che gli interi globali non iniziati siano inizializzati per impostazione predefinita su tutti zeri, e come tale un puntatore di tutti zeri sarebbe considerato non inizializzato.

Qui vorrei dire una parola su altre risposte

Non a causa dello zucchero sintattico

Dire che NULL è zero a causa dello zucchero sintattico, non ha molto senso, in tal caso perché non utilizzare l'indice 0 di un array per mantenerne la lunghezza?

In effetti C è il linguaggio che più somiglia all'implementazione interna, ha senso dire che C ha scelto zero solo a causa dello zucchero sintattico? Preferirebbero fornire una parola chiave null (come fanno molti altri linguaggi) piuttosto che mappare zero su NULL!

In quanto tale, mentre ad oggi potrebbe essere solo zucchero sintattico, è chiaro che l'intenzione originale degli sviluppatori del linguaggio C non era per lo zucchero sintattico, come mostrerò più avanti.

1) La specifica

Tuttavia, sebbene sia vero che la specifica C parla dalla costante 0 come puntatore nullo (sezione 6.3.2.3), e definisce anche NULL da definire come implementazione (sezione 7.19 nella specifica C11 e 7.17 nella specifica C99), la Resta il fatto che nel libro "The C Programming Language" scritto dagli inventori di C nella sezione 5.4 si afferma quanto segue:

C garantisce che zero non sia mai un indirizzo valido per i dati, quindi un valore di ritorno pari a zero può essere utilizzato per segnalare un evento anormale, in questo caso, senza spazio.

Puntatore e numeri interi non sono intercambiabili, Zero è l'unica eccezione: la costante zero può essere assegnata a un puntatore e un puntatore può essere confrontato con la costante zero. La costante simbolica NULL è spesso usata al posto di zero, come mnemonico per indicare più chiaramente che questo è un valore speciale per un puntatore. NULL è definito in. D'ora in poi useremo NULL.

Come si può vedere (dalle parole "indirizzo zero") almeno l'intenzione originaria degli autori di C era dell'indirizzo zero, e non della costante zero, inoltre da questo estratto risulta che il motivo per cui la specifica parla dal la costante zero probabilmente non serve per escludere un'espressione che restituisce zero, ma piuttosto per includere la costante intera zero come l'unica costante intera consentita per l'uso in un contesto di puntatore senza casting.

2) Riepilogo

Sebbene la specifica non dica esplicitamente che un indirizzo zero può essere trattato in modo diverso dalla costante zero, non lo dice, e il fatto che quando si tratta della costante del puntatore nullo non pretenda che sia un'implementazione definita come essa fa dalla costante definita NULL , invece affermare che sia zero, mostra che potrebbe esserci una differenza tra la costante zero e l'indirizzo zero.

(Tuttavia, se questo è il caso, mi chiedo solo perché NULL sia definita l'implementazione, poiché in tal caso NULL può anche essere la costante zero, poiché il compilatore deve comunque convertire tutte le costanti zero nell'attuale implementazione definita NULL?)

Tuttavia non lo vedo in azione reale e nelle piattaforme generali l'indirizzo zero e la costante zero sono trattati allo stesso modo e lanciano lo stesso messaggio di errore.

Inoltre il fatto è che i sistemi operativi odierni stanno effettivamente riservando l'intera prima pagina (range da 0x0000 a 0xFFFF), solo per impedire l'accesso all'indirizzo zero a causa del puntatore NULL di C, (vedi http://en.wikipedia.org/wiki/ Zero_page , così come "Windows tramite C / C ++ di Jeffrey Richter e Christophe Nasarre (pubblicato da Microsoft Press)").

Quindi chiederei a chiunque affermi di averlo effettivamente visto in azione, di specificare la piattaforma e il compilatore e il codice esatto che ha effettivamente fatto, (sebbene a causa della vaga definizione nelle specifiche [come ho mostrato] qualsiasi compilatore e la piattaforma è libera di fare quello che vuole).

Tuttavia sembra che gli autori di C non lo avessero in mente, e stessero parlando dell '"indirizzo zero", e che "C garantisce che non sia mai un indirizzo valido", così come "NULL è solo un mnemonico ", mostrando chiaramente che l'intenzione originale non era per" zucchero sintattico ".

Non a causa del sistema operativo

Affermando inoltre che il sistema operativo nega l'accesso all'indirizzo zero, per alcuni motivi:

1) Quando è stato scritto C non c'era alcuna restrizione di questo tipo, come si può vedere su questo wikipage http://en.wikipedia.org/wiki/Zero_page .

2) Il fatto è che i compilatori C hanno avuto accesso all'indirizzo di memoria zero.

Questo sembra essere il fatto dal seguente articolo di BellLabs ( http://www.cs.bell-labs.com/who/dmr/primevalC.html )

I due compilatori differiscono nei dettagli nel modo in cui affrontano questo problema. Nella prima, l'inizio si trova nominando una funzione; in seguito, l'inizio è semplicemente considerato 0. Ciò indica che il primo compilatore è stato scritto prima che avessimo una macchina con mappatura della memoria, quindi l'origine del programma non era nella posizione 0, mentre al momento del secondo, avevamo un PDP-11 che forniva la mappatura.

(In effetti ad oggi (come ho citato sopra i riferimenti da wikipedia e microsoft press), il motivo per limitare l'accesso all'indirizzo zero è a causa dei puntatori NULL di C! Quindi alla fine risulta essere il contrario!)

3) Ricorda che C è anche usato per scrivere sistemi operativi e persino compilatori C!

In effetti C è stato sviluppato allo scopo di scrivere il sistema operativo UNIX con esso, e come tale non sembra essere una ragione per cui dovrebbero limitarsi dall'indirizzo zero.

Spiegazione (hardware) su come i computer sono (fisicamente) in grado di accedere all'indirizzo zero

C'è un altro punto che voglio spiegare qui, come è possibile fare riferimento all'indirizzo zero?

Pensaci per un secondo, gli indirizzi vengono recuperati dal processore e quindi inviati come tensioni sul bus di memoria, che viene quindi utilizzato dal sistema di memoria per raggiungere l'indirizzo effettivo, e tuttavia un indirizzo pari a zero significherà assenza di tensione , allora in che modo l'hardware fisico del sistema di memoria accede all'indirizzo zero?

La risposta sembra essere che l'indirizzo zero è l'impostazione predefinita, e in altre parole l'indirizzo zero è sempre accessibile dal sistema di memoria quando il bus di memoria è completamente spento, e come tale qualsiasi richiesta di lettura o scrittura senza specificare un indirizzo effettivo (che è il caso con indirizzo zero) accede automaticamente all'indirizzo zero.


1
Non ti ho sottovalutato, ma il tuo post ha diverse inesattezze fattuali, ad es. che la memoria fisica all'offset 0 è impossibile accedere (a causa di tutti gli interruttori sono spenti? davvero?), 0 e la costante 0 è intercambiabile (potrebbero non esserlo) e altri.
Hasturkun

Per quanto riguarda lo 0 e lo zero costante, questo è ciò che dice il libro originale, e questo ciò che dimostrano i test effettivi, hai trovato una vera differenza tra i due? Se sì, quale compilatore e piattaforma? Sebbene molte risposte suggeriscano che c'è una differenza, io non l'ho trovata e non hanno alcun riferimento per mostrare una differenza. Infatti secondo en.wikipedia.org/wiki/Zero_page E anche "Windows Via C / C ++ di Jeffrey Richter e Christophe Nasarre (pubblicato da Microsoft Press)" l'intera prima pagina! è protetto nei computer moderni solo per prevenire null (in realtà sprecare più di un byte!)
yoel halb

Ovviamente la sequenza di bit dell'indirizzo viene utilizzata per selezionare ciò che viene letto. In genere è così. in ogni caso, non voglio discutere con te, stavo solo facendo notare perché potresti essere stato svalutato.
Hasturkun

Non sono d'accordo con le tue affermazioni. Inoltre, non sono interessato a continuare questa discussione.
Hasturkun

6
L'affermazione hardware è una sciocchezza. Per leggere l'indirizzo zero, guidare! Chip Select basso,! RAS alto,! CAS basso,! WE alto e tutte le linee di indirizzo basse. Quando l'autobus è spento,! CS è alto.
MSalters
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.