Wo werden Nullwerte gespeichert oder werden sie überhaupt gespeichert?

39

Ich möchte mehr über Nullwerte oder Nullreferenzen erfahren.

Zum Beispiel habe ich eine Klasse namens Apple und habe eine Instanz davon erstellt.

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

Dann habe ich diesen Apfel gegessen und jetzt muss er null sein, also habe ich ihn auf null gesetzt.

myApple = null;

Nach diesem Anruf habe ich vergessen, dass ich es gegessen habe und jetzt nachschauen möchte.

bool isEaten = (myApple == null);

Wo ist bei diesem Aufruf die Referenzierung von myApple? Ist null ein spezieller Zeigerwert? Wenn ja, wenn ich 1000 Nullobjekte habe, belegen sie 1000 Objektspeicherplatz oder 1000 int Speicherplatz, wenn wir einen Zeigertyp als int betrachten?

Mert Akcakaya
quelle

Antworten:

45

In Ihrem Beispiel myApplehat der spezielle Wert null(normalerweise alle Null-Bits) und verweist daher auf nichts. Das Objekt, auf das es ursprünglich verwiesen hat, ist jetzt auf dem Heap verloren. Es gibt keine Möglichkeit, seinen Standort abzurufen. Dies ist bei Systemen ohne Garbage Collection als Speicherverlust bekannt.

Wenn Sie ursprünglich 1000 Referenzen auf Null gesetzt haben, haben Sie nur Platz für 1000 Referenzen, normalerweise 1000 * 4 Bytes (auf einem 32-Bit-System doppelt so viel wie auf 64). Wenn diese 1000 Referenzen ursprünglich auf reale Objekte verweisen, haben Sie jedem Objekt die 1000-fache Größe zuzüglich des Platzes für die 1000 Referenzen zugewiesen.

In einigen Sprachen (wie C und C ++) zeigen Zeiger immer auf etwas, auch wenn sie "nicht initialisiert" sind. Die Frage ist, ob die Adresse, die sie haben, für den Zugriff auf Ihr Programm zulässig ist. Die spezielle Adresse Null (aka null) wird bewusst nicht in Ihren Adressraum abgebildet, sodass die Speicherverwaltungseinheit (MMU) einen Segmentierungsfehler generiert, wenn auf sie zugegriffen wird und Ihr Programm abstürzt. Da die Adresse Null jedoch absichtlich nicht zugeordnet ist, wird sie zu einem idealen Wert, der angibt, dass ein Zeiger auf nichts zeigt, daher seine Rolle als null. Vervollständigen Sie die Geschichte, indem Sie mit newoder Speicher zuweisenmalloc()Das Betriebssystem konfiguriert die MMU so, dass Seiten des Arbeitsspeichers Ihrem Adressraum zugeordnet werden und diese dann verwendet werden können. Es gibt in der Regel immer noch große Adressbereiche, die nicht zugeordnet sind und daher auch zu Segmentierungsfehlern führen.

Randall Cook
quelle
Sehr gute Erklärung.
NoChance
6
Es ist etwas falsch auf dem "Memory Leak" Teil. Dies ist ein Speicherverlust in Systemen ohne automatische Speicherverwaltung. GC ist jedoch nicht die einzige Möglichkeit, die automatische Speicherverwaltung zu implementieren. C ++ std::shared_ptr<Apple>ist ein Beispiel, das weder GC noch Leak Applebeim Nullsetzen enthält.
MSalters
1
@MSalters - Ist das nicht shared_ptrnur eine Grundform für die Garbage Collection? GC erfordert nicht, dass es einen separaten "Garbage Collector" gibt, sondern nur, dass eine Garbage Collection stattfindet.
Setzen Sie Monica
5
@Brendan: Der Begriff "Speicherbereinigung" wird fast allgemein als nicht deterministische Sammlung verstanden, die unabhängig vom normalen Codepfad stattfindet. Die deterministische Zerstörung durch Referenzzählung ist etwas völlig anderes.
Mason Wheeler
1
Gute Erklärung. Ein etwas irreführender Punkt ist die Annahme, dass die Speicherzuordnung dem RAM zugeordnet ist. RAM ist ein Mechanismus für die Kurzzeitspeicherung, der eigentliche Speichermechanismus wird jedoch vom Betriebssystem abstrahiert. In Windows (für Nicht-Ring-Zero-Apps) werden die Speicherseiten virtualisiert und können dem RAM, der Auslagerungsdatei oder einem anderen Speichergerät zugeordnet werden.
Simon Gillbee
13

Die Antwort hängt von der Sprache ab, die Sie verwenden.

C / C ++

In C und C ++ war das Schlüsselwort NULL, und was NULL wirklich war, war 0. Es wurde entschieden, dass "0x0000" niemals ein gültiger Zeiger auf ein Objekt sein würde, und das ist der Wert, der zugewiesen wird, um dies anzuzeigen ist kein gültiger Zeiger. Es ist jedoch völlig willkürlich. Wenn Sie versuchen, wie ein Zeiger darauf zuzugreifen, verhält es sich genauso wie ein Zeiger auf ein Objekt, das nicht mehr im Speicher vorhanden ist, wodurch eine ungültige Zeigerausnahme ausgelöst wird. Der Zeiger selbst belegt Speicher, aber nicht mehr als ein ganzzahliges Objekt. Wenn Sie also 1000 Nullzeiger haben, entspricht dies 1000 ganzen Zahlen. Wenn einige dieser Zeiger auf gültige Objekte verweisen, entspricht die Speichernutzung 1000 ganzen Zahlen plus dem in diesen gültigen Zeigern enthaltenen Speicher. Denken Sie daran, dass in C oder C ++,Es wurde kein impliziter Speicher freigegeben, daher müssen Sie dieses Objekt explizit mit dealloc (C) oder delete (C ++) löschen.

Java

Anders als in C und C ++ ist in Java null nur ein Schlüsselwort. Anstatt null wie einen Zeiger auf ein Objekt zu verwalten, wird es intern verwaltet und wie ein Literal behandelt. Dies beseitigt die Notwendigkeit, Zeiger als Integer-Typen einzubinden, und ermöglicht es Java, Zeiger vollständig zu abstrahieren. Auch wenn Java es besser verbirgt, handelt es sich dennoch um Zeiger, was bedeutet, dass 1000 Nullzeiger immer noch das Äquivalent von 1000 ganzen Zahlen verbrauchen. Offensichtlich wird, wenn sie auf Objekte verweisen, ähnlich wie in C und C ++, Speicher von diesen Objekten belegt, bis keine Zeiger mehr auf sie verweisen. In den meisten Fällen müssen Sie nicht nachverfolgen, welche Objekte freigegeben sind und welche nicht (es sei denn, Sie haben Gründe, auf Objekte schwach zu verweisen).

Neil
quelle
9
Ihre Unterscheidung ist nicht korrekt: In C und C ++ muss der Nullzeiger überhaupt nicht auf die Speicheradresse 0 verweisen (obwohl dies die natürliche Implementierung ist, wie in Java und C #). Es kann buchstäblich überall zeigen. Dies wird leicht durch die Tatsache verwirrt, dass Literal-0 implizit in einen Nullzeiger konvertiert werden kann. Das für einen Nullzeiger gespeicherte Bitmuster muss jedoch noch nicht vollständig aus Nullen bestehen.
Konrad Rudolph
1
Nein, du liegst falsch. Die Semantik ist völlig transparent ... im Programm werden Nullzeiger und das Makro NULL( übrigens kein Schlüsselwort) so behandelt, als wären sie Null-Bits. Aber sie müssen nicht als solche umgesetzt werden, und in der Tat einige obskure Implementierungen tun Verwendung nicht-Null - Null - Zeiger. Wenn ich schreibe, if (myptr == 0)wird der Compiler das Richtige tun, auch wenn der Nullzeiger intern durch dargestellt wird 0xabcdef.
Konrad Rudolph
3
@Neil: Eine Nullzeiger-Konstante (Wert vom Typ Integer, der zu Null ausgewertet wird) kann in einen Nullzeiger-Wert konvertiert werden . (§4.10 C ++ 11.) Es ist nicht garantiert, dass ein Nullzeigerwert alle Bits Null hat. 0ist eine Nullzeiger-Konstante, dies bedeutet jedoch nicht, dass myptr == 0überprüft wird, ob alle Bits von myptrNull sind.
Mat
5
@Neil: Vielleicht möchten Sie diesen Eintrag in der C-FAQ oder in dieser SO-Frage
überprüfen
1
@Neil Deshalb habe ich mir Mühe gegeben, das NULLMakro überhaupt nicht zu erwähnen , sondern über den „Nullzeiger“ zu sprechen und ausdrücklich zu erwähnen, dass „Literal-0 implizit in einen Nullzeiger konvertiert werden kann“.
Konrad Rudolph
5

Ein Zeiger ist einfach eine Variable, die meist vom Typ Integer ist. Es gibt eine Speicheradresse an, unter der das eigentliche Objekt gespeichert ist.

Die meisten Sprachen erlauben den Zugriff auf Objektmitglieder über diese Zeigervariable:

int localInt = myApple.appleInt;

Der Compiler weiß, wie er auf die Mitglieder von an zugreift Apple. Es "folgt" dem Zeiger auf myAppledie Adresse und ruft den Wert von abappleInt

Wenn Sie den Nullzeiger einer Zeigervariablen zuweisen, weisen Sie den Zeiger auf keine Speicheradresse. (Dies macht den Zugang für Mitglieder unmöglich.)

Für jeden Zeiger benötigen Sie Speicher, um den Integer-Wert der Speicheradresse zu speichern (meistens 4 Bytes bei 32-Bit-Systemen, 8 Bytes bei 64-Bit-Systemen). Dies gilt auch für Nullzeiger.

Stephan
quelle
Ich denke, die Referenzvariablen / -objekte sind nicht genau Zeiger. Wenn Sie sie ausdrucken, enthalten sie ClassName @ Hashcode. JVM verwendet Hashtable intern, um Hashcode mit der tatsächlichen Adresse zu speichern, und verwendet einen Hash-Algorithmus, um die tatsächliche Adresse bei Bedarf abzurufen.
minusSeven
@minusSeven Das ist richtig für wörtliche Objekte wie Ganzzahlen. Andernfalls enthält die Hashtabelle Zeiger auf andere Objekte, die in der Apple-Klasse selbst enthalten sind.
Neil
@minusSeven: Ich stimme zu. Die Details der Zeigerimplementierung hängen stark von der Sprache / Laufzeit ab. Aber ich denke, diese Details sind für die spezifische Frage nicht so relevant.
Stephan
4

Schnelles Beispiel (Notizvariablennamen werden nicht gespeichert):

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


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

Prost.

umlcat
quelle