Dove sono archiviati i valori null o sono archiviati?


39

Voglio conoscere valori nulli o riferimenti null.

Ad esempio ho una classe chiamata Apple e ne ho creato un'istanza.

Apple myApple = new Apple("yummy"); // The data is stored in memory

Poi ho mangiato quella mela e ora deve essere nulla, quindi l'ho impostata come nulla.

myApple = null;

Dopo questa chiamata, ho dimenticato di averlo mangiato e ora voglio controllare.

bool isEaten = (myApple == null);

Con questa chiamata, dove si riferisce myApple? Null è un valore puntatore speciale? In tal caso, se ho 1000 oggetti null, occupano lo spazio di memoria di 1000 oggetti o lo spazio di memoria di 1000 int se pensiamo che un tipo di puntatore sia int?

Risposte:


45

Nel tuo esempio myApple ha il valore speciale null(in genere tutti i bit zero) e quindi non fa riferimento a nulla. L'oggetto a cui si riferiva originariamente ora è perso nell'heap. Non è possibile recuperarne la posizione. Ciò è noto come perdita di memoria nei sistemi senza garbage collection.

Se in origine hai impostato 1000 riferimenti su null, allora hai spazio per soli 1000 riferimenti, in genere 1000 * 4 byte (su un sistema a 32 bit, il doppio rispetto a 64). Se quei 1000 riferimenti inizialmente puntavano a oggetti reali, allora hai allocato 1000 volte la dimensione di ogni oggetto, più lo spazio per i 1000 riferimenti.

In alcuni linguaggi (come C e C ++), i puntatori puntano sempre a qualcosa, anche se "non inizializzati". Il problema è se l'indirizzo che detengono è legale per l'accesso al tuo programma. L'indirizzo speciale zero (aka null) non viene deliberatamente mappato nello spazio degli indirizzi, quindi un errore di segmentazione viene generato dall'unità di gestione della memoria (MMU) quando si accede e il programma si arresta in modo anomalo. Ma poiché l'indirizzo zero viene volutamente non mappata, diventa un valore ideale da utilizzare per indicare che un puntatore non punta a nulla, quindi il suo ruolo null. Per completare la storia, allocare memoria con newomalloc(), il sistema operativo configura la MMU per mappare le pagine di RAM nello spazio degli indirizzi e diventano utilizzabili. Esistono in genere ampi intervalli di spazio degli indirizzi che non sono mappati e quindi comportano anche errori di segmentazione.


Ottima spiegazione
NoChance,

6
È leggermente sbagliato nella parte "perdita di memoria". È una perdita di memoria nei sistemi senza gestione automatica della memoria. Tuttavia, GC non è l'unico modo possibile per implementare la gestione automatica della memoria. Il C ++ std::shared_ptr<Apple>è un esempio che non è né GC né perde Applequando è azzerato.
MSalters

1
@MSalters - Non è shared_ptrsolo un modulo base per la raccolta dei rifiuti? GC non richiede l'esistenza di un "garbage collector" separato, ma solo che si verifica la garbage collection.
Ripristina Monica

5
@Brendan: il termine "garbage collection" è quasi universalmente inteso come riferimento alla raccolta non deterministica che ha luogo indipendentemente dal normale percorso del codice. La distruzione deterministica basata sul conteggio dei riferimenti è qualcosa di completamente diverso.
Mason Wheeler,

1
Buona spiegazione Un punto leggermente fuorviante è l'assunto che l'allocazione della memoria si associ alla RAM. La RAM è un meccanismo per l'archiviazione della memoria a breve termine, ma il meccanismo di archiviazione effettivo viene estratto dal sistema operativo. In Windows (per le app senza zero) le pagine di memoria sono virtualizzate e possono essere mappate su RAM, file di scambio del disco o forse su un altro dispositivo di archiviazione.
Simon Gillbee,

13

La risposta dipende dalla lingua che stai utilizzando.

C / C ++

In C e C ++, la parola chiave era NULL e ciò che NULL era in realtà era 0. Si decise che "0x0000" non sarebbe mai stato un puntatore valido per un oggetto, e quindi questo è il valore che viene assegnato per indicarlo non è un puntatore valido. Tuttavia, è completamente arbitrario. Se si tentasse di accedervi come un puntatore, si comporterebbe esattamente come un puntatore a un oggetto che non esiste più in memoria, causando il lancio di un'eccezione del puntatore non valida. Il puntatore stesso occupa memoria, ma non più di quanto farebbe un oggetto intero. Quindi, se hai 1000 puntatori null, è l'equivalente di 1000 numeri interi. Se alcuni di questi puntatori puntano a oggetti validi, l'uso della memoria sarebbe l'equivalente di 1000 numeri interi più la memoria contenuta in quei puntatori validi. Ricorda che in C o C ++,non implica che la memoria sia stata rilasciata, quindi è necessario eliminare esplicitamente quell'oggetto usando dealloc (C) o delete (C ++).

Giava

A differenza di C e C ++, in Java null è semplicemente una parola chiave. Invece di gestire null come un puntatore a un oggetto, viene gestito internamente e trattato come un valore letterale. Ciò ha eliminato la necessità di collegare i puntatori come tipi interi e consente a Java di sottrarre completamente i puntatori. Tuttavia, anche se Java lo nasconde meglio, sono comunque puntatori, il che significa che 1000 puntatori null consumano comunque l'equivalente di 1000 numeri interi. Ovviamente quando puntano a oggetti, proprio come C e C ++, la memoria viene consumata da quegli oggetti fino a quando non ci sono più puntatori che li fanno riferimento, tuttavia a differenza di C e C ++, il garbage collector lo raccoglie al suo passaggio successivo e libera la memoria, senza che sia necessario tenere traccia di quali oggetti vengono liberati e quali non lo sono, nella maggior parte dei casi (a meno che non si abbiano motivi per fare riferimento debolmente agli oggetti, ad esempio).


9
La tua distinzione non è corretta: in effetti, in C e C ++, il puntatore null non deve affatto indirizzare l'indirizzo di memoria 0 (sebbene questa sia l'implementazione naturale, come in Java e C #). Può puntare letteralmente ovunque. Ciò è leggermente confuso dal fatto che letterale-0 può essere implicitamente convertito in un puntatore nullo. Ma il modello di bit memorizzato per un puntatore nullo non deve ancora essere tutti zeri.
Konrad Rudolph,

1
No, hai torto. La semantica è completamente trasparente ... nel programma, i puntatori null e la macro NULL( non una parola chiave, a proposito) sono trattati come se fossero zero-bit. Ma loro non hanno bisogno di essere implementato come tale, e in effetti alcune implementazioni oscuri fanno uso puntatori nulli diversi da zero. Se scrivo, if (myptr == 0)il compilatore farà la cosa giusta, anche se il puntatore null è rappresentato internamente da 0xabcdef.
Konrad Rudolph,

3
@Neil: una costante puntatore null (valore di tipo intero che valuta zero) è convertibile in un valore puntatore null . (§4.10 C ++ 11.) Un valore di puntatore null non è garantito per avere tutti i bit zero. 0è una costante puntatore nulla, ma ciò non significa che myptr == 0controlla se tutti i bit di myptrsono zero.
Mat

5
@Neil: potresti voler controllare questa voce nella faq C o questa domanda SO
hugomg

1
@Neil Ecco perché mi sono preso la briga di non menzionare affatto la NULLmacro, piuttosto di parlare del "puntatore nullo" e di menzionare esplicitamente che "letterale-0 può essere implicitamente convertito in un puntatore nullo".
Konrad Rudolph,

5

Un puntatore è semplicemente una variabile che è principalmente di tipo intero. Specifica un indirizzo di memoria in cui è archiviato l'oggetto reale.

La maggior parte delle lingue consente di accedere ai membri dell'oggetto tramite questa variabile puntatore:

int localInt = myApple.appleInt;

Il compilatore sa come accedere ai membri di un Apple . "Segue" il puntatore myAppleall'indirizzo e recupera il valore diappleInt

Se si assegna il puntatore null a una variabile puntatore, si fa puntare il puntatore su nessun indirizzo di memoria. (Il che rende impossibile l'accesso ai membri.)

Per ogni puntatore è necessaria la memoria per contenere il valore intero dell'indirizzo di memoria (principalmente 4 byte su sistemi a 32 bit, 8 byte su sistemi a 64 bit). Questo vale anche per i puntatori null.


Penso che le variabili / gli oggetti di riferimento non siano esattamente puntatori. Se li stampi contengono ClassName @ Hashcode. JVM utilizza internamente Hashtable per archiviare Hashcode con l'indirizzo effettivo e utilizza un algoritmo hash per recuperare l'indirizzo effettivo quando necessario.
menoSeven

@minusSeven È corretto per ciò che riguarda oggetti letterali come numeri interi. Altrimenti la tabella hash contiene puntatori ad altri oggetti contenuti nella stessa classe Apple.
Neil

@minusSeven: sono d'accordo. I dettagli dell'implementazione del puntatore dipendono fortemente dalla lingua / dal runtime. Ma penso che quei dettagli non siano così rilevanti per la domanda specifica.
Stephan,

4

Esempio rapido (i nomi delle variabili nota non vengono memorizzati):

void main()
{
  int X = 3;
  int *Y = X;
  int *Z = null;
} // void main(...)


...........................
....+-----+--------+.......
....|     |   X    |.......
....+-----+--------+.......
....| 100 |   3    |<---+..
....+-----+--------+....|..
........................|..
....+-----+--------+....|..
....|     |   Y    |....|..
....+-----+--------+....|..
....| 102 |  100   +----+..
....+-----+--------+.......
...........................
....+-----+--------+.......
....|     |   z    |.......
....+-----+--------+.......
....| 104 |   0    |.......
....+-----+--------+.......
...........................

Saluti.

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.