Warum wird die Adresse Null für den Nullzeiger verwendet?

121

In C (oder C ++) sind Zeiger etwas Besonderes, wenn sie den Wert Null haben: Es wird empfohlen, Zeiger nach dem Freigeben des Speichers auf Null zu setzen, da dies bedeutet, dass das erneute Freigeben des Zeigers nicht gefährlich ist. Wenn ich malloc aufrufe, wird ein Zeiger mit dem Wert Null zurückgegeben, wenn ich keinen Speicher erhalten kann. Ich benutze die if (p != 0)ganze Zeit, um sicherzustellen, dass übergebene Zeiger gültig sind usw.

Aber da die Speicheradressierung bei 0 beginnt, ist 0 nicht genauso eine gültige Adresse wie jede andere? Wie kann 0 für den Umgang mit Nullzeigern verwendet werden, wenn dies der Fall ist? Warum ist stattdessen keine negative Zahl null?


Bearbeiten:

Eine Menge guter Antworten. Ich werde zusammenfassen, was in den Antworten gesagt wurde, die so ausgedrückt wurden, wie mein eigener Verstand es interpretiert, und hoffe, dass die Community mich korrigiert, wenn ich es falsch verstehe.

  • Wie alles andere in der Programmierung ist es eine Abstraktion. Nur eine Konstante, die nicht wirklich mit der Adresse 0 zusammenhängt. C ++ 0x betont dies durch Hinzufügen des Schlüsselworts nullptr.

  • Es ist nicht einmal eine Adressabstraktion, es ist die vom C-Standard festgelegte Konstante, und der Compiler kann sie in eine andere Zahl übersetzen, solange er sicherstellt, dass sie niemals einer "echten" Adresse entspricht, und anderen Nullzeigern entspricht, wenn 0 nicht die ist bester Wert für die Plattform.

  • Falls es sich nicht um eine Abstraktion handelt, wie dies früher der Fall war, wird die Adresse 0 vom System verwendet und ist für den Programmierer nicht zulässig.

  • Ich gebe zu, mein Vorschlag für eine negative Zahl war ein wenig wildes Brainstorming. Die Verwendung einer vorzeichenbehafteten Ganzzahl für Adressen ist etwas verschwenderisch, wenn der Wertraum abgesehen vom Nullzeiger (-1 oder was auch immer) gleichmäßig zwischen positiven Ganzzahlen, die gültige Adressen ergeben, und negativen Zahlen, die nur verschwendet werden, aufgeteilt wird.

  • Wenn eine Zahl immer durch einen Datentyp darstellbar ist, ist sie 0. (Wahrscheinlich ist 1 auch. Ich denke an die Ein-Bit-Ganzzahl, die 0 oder 1 wäre, wenn sie nicht vorzeichenbehaftet wäre, oder nur an das vorzeichenbehaftete Bit, wenn sie vorzeichenbehaftet ist, oder an die Zwei-Bit-Ganzzahl, die wäre [-2, 1]. Aber dann könnte man einfach 0 als Null und 1 als einziges zugängliches Byte im Speicher wählen.)

Trotzdem gibt es etwas, das in meinem Kopf ungelöst ist. Der Stapelüberlauf-Fragezeiger auf eine bestimmte feste Adresse sagt mir, dass andere Zeigerwerte nicht unbedingt erforderlich sind, selbst wenn 0 für Nullzeiger eine Abstraktion ist. Dies führt mich zu einer weiteren Frage zum Stapelüberlauf: Könnte ich jemals auf die Adresse Null zugreifen wollen? .

Joel
quelle
11
Sie können genauso gut if (p != 0)zu if (p)einer in C und C ++ gebräuchlichen Redewendung wechseln , obwohl Sie sich aus der Gewohnheit befreien müssen, wenn Sie Java verwenden.
Paul Tomblin
14
Wenn Sie etwas zweimal löschen, ist Ihr Code falsch. Ich rate davon ab, Zeiger danach auf null zu setzen, damit Sie abstürzen und das Problem beheben können, ohne es zu unterdrücken. In jedem Fall machen Sie den Fehler anzunehmen, dass eine Adresse eine ganze Zahl ist. Dies ist nicht unbedingt der Fall, und 0 steht lediglich für einen tatsächlichen Zeigerwert, der implementierungsspezifisch ist. Eine "negative Adresse" macht konzeptionell keinen Sinn.
GManNickG
6
@GMan: Vielleicht sogar eine gute Idee, den Zeiger auf eine Adresse zu setzen, die einen Absturz erzwingt, wie z 0xDEADBEEF.
Billy ONeal
5
Die Frage, die niemals sterben wird!
8
@Noah: Der Punkt ist auf null gesetzt -> Programmierfehler ausblenden, nicht auf null setzen -> Programmierfehler finden. Ich weiß nichts über dich, aber ich möchte, dass mein Code korrekt ist.
GManNickG

Antworten:

65

2 Punkte:

  • Nur der konstante Wert 0 im Quellcode ist der Nullzeiger. Die Compiler-Implementierung kann den gewünschten Wert im laufenden Code verwenden. Einige Plattformen haben einen speziellen Zeigerwert, der "ungültig" ist und den die Implementierung möglicherweise als Nullzeiger verwendet. In den C-FAQs wird die Frage gestellt: "Im Ernst, haben tatsächlich Maschinen wirklich Nullzeiger ungleich Null oder unterschiedliche Darstellungen für Zeiger auf verschiedene Typen verwendet?" Dies weist auf mehrere Plattformen hin, die diese Eigenschaft von 0 als Nullzeiger in der C-Quelle verwendet haben, während sie zur Laufzeit unterschiedlich dargestellt werden. Der C ++ - Standard enthält einen Hinweis, der deutlich macht, dass das Konvertieren eines integralen konstanten Ausdrucks mit dem Wert Null immer einen Nullzeiger ergibt.

  • Ein negativer Wert kann von der Plattform genauso verwendet werden wie eine Adresse - der C-Standard musste lediglich etwas auswählen, um einen Nullzeiger anzuzeigen, und Null wurde ausgewählt. Ich bin mir ehrlich gesagt nicht sicher, ob andere Sentinel-Werte berücksichtigt wurden.

Die einzigen Voraussetzungen für einen Nullzeiger sind:

  • Es ist garantiert ungleich mit einem Zeiger auf ein tatsächliches Objekt zu vergleichen
  • Zwei beliebige Nullzeiger werden gleich verglichen (C ++ verfeinert dies so, dass dies nur für Zeiger desselben Typs gelten muss).
Michael Burr
quelle
12
+1 Ich vermute, 0 wurde nur aus historischen Gründen gewählt. (0 ist meistens eine Startadresse und eine ungültige Adresse.) Natürlich ist eine solche Annahme im Allgemeinen nicht immer wahr, aber 0 funktioniert ziemlich gut.
GManNickG
8
Der Weltraum könnte auch ein Faktor gewesen sein. In den Tagen, als C zum ersten Mal entwickelt wurde, war Speicher VIEL teurer als jetzt. Die Zahl Null kann bequem mit einem XOR-Befehl oder ohne Laden eines Sofortwerts berechnet werden. Abhängig von der Architektur kann dies möglicherweise Platz sparen.
Sparky
6
@ GMan - Sie sind richtig. Bei frühen CPUs war die Speicheradresse Null etwas Besonderes und hatte einen Hardwareschutz gegen den Zugriff durch laufende Software (in einigen Fällen war dies der Beginn des Rücksetzvektors, und eine Änderung konnte verhindern, dass die CPU zurückgesetzt oder gestartet wurde). Programmierer verwendeten diesen Hardwareschutz als eine Form der Fehlererkennung in ihrer Software und ließen die Adressdecodierungslogik der CPU auf nicht initialisierte oder ungültige Zeiger prüfen, anstatt dafür CPU-Anweisungen ausgeben zu müssen. Die Konvention bleibt bis heute bestehen, auch wenn sich der Zweck der Adresse Null möglicherweise geändert hat.
Bis
10
Der Minix 16-Bit-Compiler verwendete 0xFFFF für NULL.
Joshua
3
In vielen eingebetteten Systemen ist 0 eine gültige Adresse. Der Wert -1 (alle Bits eins) ist ebenfalls eine gültige Adresse. Prüfsummen für ROMs sind schwer zu berechnen, wenn die Daten bei Adresse 0 beginnen. :-(
Thomas Matthews
31

In der Vergangenheit war der Adressraum ab 0 immer ein ROM, der für einige Betriebssystem- oder Interrupt-Behandlungsroutinen auf niedriger Ebene verwendet wurde. Da alles virtuell ist (einschließlich des Adressraums), kann das Betriebssystem jede Zuordnung einer beliebigen Adresse zuordnen, so dass dies möglich ist Weisen Sie an der Adresse 0 NICHT zu.

Aviad P.
quelle
6
Das ist es so ziemlich. Es handelt sich um eine historische Konvention, und die ersten Adressen wurden für Interrupt-Handler verwendet und sind daher für normale Programme unbrauchbar. Außerdem ist 0 "leer", was als kein Wert / kein Zeiger interpretiert werden kann.
TomTom
15

IIRC, der "Nullzeiger" -Wert ist nicht garantiert Null. Der Compiler übersetzt 0 in den für das System geeigneten "Null" -Wert (der in der Praxis wahrscheinlich immer Null ist, aber nicht unbedingt). Die gleiche Übersetzung wird angewendet, wenn Sie einen Zeiger mit Null vergleichen. Da Sie nur Zeiger miteinander und mit diesem Sonderwert 0 vergleichen können, wird der Programmierer davon abgehalten, etwas über die Speicherdarstellung des Systems zu wissen. Warum sie 0 anstelle von 42 oder so gewählt haben, liegt vermutlich daran, dass die meisten Programmierer bei 0 anfangen zu zählen :) (Außerdem ist auf den meisten Systemen 0 die erste Speicheradresse und sie wollten, dass sie praktisch ist, da in Übungsübersetzungen, wie ich sie beschreibe, finden selten tatsächlich statt (die Sprache erlaubt sie nur).

rmeador
quelle
5
@ Justin: Du hast es falsch verstanden. Die Konstante 0 ist immer der Nullzeiger. Was @meador sagt, ist, dass es möglich ist, dass der Nullzeiger (angezeigt durch die Konstante 0) nicht der Adresse Null entspricht. Auf einigen Plattformen kann durch das Erstellen eines Nullzeigers ( int* p = 0) ein Zeiger erstellt werden, der den Wert 0xdeadbeefoder einen anderen von ihm bevorzugten Wert enthält. 0 ist ein Nullzeiger, aber ein Nullzeiger ist nicht unbedingt ein Zeiger auf die Adresse Null. :)
Jalf
Ein NULL-Zeiger ist ein reservierter Wert und kann je nach Compiler ein beliebiges Bitmuster sein. NULL Zeiger bedeutet nicht, dass er auf Adresse 0 zeigt.
Sharjeel Aziz
3
Aber @Jalf, die Konstante 0 ist nicht immer der Nullzeiger. Es ist das, was wir schreiben, wenn der Compiler den tatsächlichen Nullzeiger der Plattform für uns ausfüllen soll . Praktisch gesprochen, der Null - Zeiger in der Regel tut , entsprechen der Adresse Null, obwohl, und ich interpretieren Joels Frage zu fragen , warum das so ist. Schließlich befindet sich an dieser Adresse angeblich ein gültiges Speicherbyte. Warum also nicht eine nicht vorhandene Adresse eines nicht vorhandenen Bytes verwenden, anstatt ein gültiges Byte aus dem Spiel zu entfernen? (Ich schreibe, was ich mir vorstelle, was Joel gedacht hat, keine Frage, die ich mir stelle.)
Rob Kennedy
@Rob: Irgendwie. Ich weiß, was Sie meinen, und Sie haben Recht, aber ich auch. :) Die konstante Ganzzahl 0 repräsentiert den Nullzeiger auf Quellcodeebene. Der Vergleich eines Nullzeigers mit 0 ergibt true. Wenn Sie einem Zeiger 0 zuweisen, wird dieser Zeiger auf Null gesetzt. 0 ist der Nullzeiger. Die tatsächliche speicherinterne Darstellung eines Nullzeigers kann sich jedoch vom Nullbitmuster unterscheiden. (Wie auch immer, mein Kommentar war eine Antwort auf @ Justins jetzt gelöschten Kommentar, nicht auf @ Joels Frage. :)
Jalf
@jalf @Rob Du brauchst ein paar Begriffe, um das zu klären, denke ich. :) Aus §4.10 / 1: "Eine Nullzeigerkonstante ist ein integraler Konstantenausdruck rWert vom ganzzahligen Typ, der zu Null ausgewertet wird. Eine Nullzeigerkonstante kann in einen Zeigertyp konvertiert werden; das Ergebnis ist der Nullzeigerwert dieses Typs und ist von jedem anderen Wert des Zeigers auf das Objekt oder des Zeigers auf den Funktionstyp unterscheidbar. "
GManNickG
15

Sie müssen die Bedeutung der Konstanten Null im Zeigerkontext falsch verstehen.

Weder in C noch in C ++ können Zeiger "den Wert Null haben". Zeiger sind keine arithmetischen Objekte. Sie können keine numerischen Werte wie "Null" oder "Negativ" oder ähnliches haben. Ihre Aussage über "Zeiger ... haben den Wert Null" macht also einfach keinen Sinn.

In C & C ++ können Zeiger den reservierten Nullzeigerwert haben . Die tatsächliche Darstellung des Nullzeigerwerts hat nichts mit "Nullen" zu tun. Es kann absolut alles sein, was für eine bestimmte Plattform geeignet ist. Es ist wahr, dass auf den meisten Plattformen der Nullzeigerwert physikalisch durch einen tatsächlichen Nulladresswert dargestellt wird. Wenn jedoch auf einer Plattform die Adresse 0 tatsächlich für einen bestimmten Zweck verwendet wird (dh Sie müssen möglicherweise Objekte unter der Adresse 0 erstellen), ist der Nullzeigerwert auf einer solchen Plattform höchstwahrscheinlich unterschiedlich. Es könnte beispielsweise physisch als 0xFFFFFFFFAdresswert oder als 0xBAADBAADAdresswert dargestellt werden.

Unabhängig davon, wie der Nullzeigerwert auf einer bestimmten Plattform dargestellt wird, werden Sie in Ihrem Code weiterhin Nullzeiger durch Konstanten festlegen 0. Um einem bestimmten Zeiger einen Nullzeigerwert zuzuweisen, verwenden Sie weiterhin Ausdrücke wie p = 0. Es liegt in der Verantwortung des Compilers, zu realisieren, was Sie wollen, und es in die richtige Nullzeigerwertdarstellung zu übersetzen, dh es in den Code zu übersetzen, der den Adresswert von 0xFFFFFFFFin den Zeiger setztp setzt.

Kurz gesagt, die Tatsache, dass Sie 0in Ihrem Sorce-Code Nullzeigerwerte generieren, bedeutet nicht, dass der Nullzeigerwert irgendwie an die Adresse gebunden ist 0. Das 0, was Sie in Ihrem Quellcode verwenden, ist nur "syntaktischer Zucker", der absolut keine Beziehung zur tatsächlichen physischen Adresse hat, auf die der Nullzeigerwert "zeigt".

Ameise
quelle
3
<quote> Zeiger sind keine arithmetischen Objekte </ quote> Zeigerarithmetik ist in C und C ++ recht gut definiert. Teil der Anforderung ist, dass beide Zeiger innerhalb desselben Verbunds zeigen. Der Nullzeiger zeigt nicht auf ein Komposit, daher ist die Verwendung in Zeigerarithmetikausdrücken unzulässig. Zum Beispiel kann dies nicht garantiert werden (p1 - nullptr) - (p2 - nullptr) == (p1 - p2).
Ben Voigt
5
@ Ben Voigt: Die Sprachspezifikation definiert den Begriff des arithmetischen Typs . Ich sage nur, dass Zeigertypen nicht zur Kategorie der arithmetischen Typen gehören. Zeigerarithmetik ist eine andere und völlig unabhängige Geschichte, ein bloßer sprachlicher Zufall.
AnT
1
Wie soll jemand, der arithmetische Objekte liest , wissen, dass dies "im Sinne von arithmetischen Typen" und nicht "im Sinne von arithmetischen Operatoren" (von denen einige für Zeiger verwendbar sind) oder "im Sinne von Zeigerarithmetik" bedeutet? In Bezug auf sprachliche Zufälle hat das arithmetische Objekt mehr Buchstaben mit der Zeigerarithmetik gemeinsam als mit arithmetischen Typen . Gleichzeitig spricht der Standard über den Zeigerwert . Das ursprüngliche Plakat wahrscheinlich gemeint Integer - Darstellung eines Zeigers anstatt Zeigerwert und NULLexplizit dargestellt werden müssen , nicht durch 0.
Ben Voigt
Nun, zum Beispiel ist der Begriff Skalarobjekte in der C / C ++ - Terminologie nur eine Abkürzung für Objekte von Skalartypen (genau wie POD-Objekte = Objekte von POD-Typen ). Ich habe den Begriff arithmetische Objekte genauso verwendet, dh Objekte arithmetischer Typen . Ich erwarte, dass "jemand" das so versteht. Jemand, der nicht immer um eine Klarstellung bitten kann.
AnT
1
Ich habe an einem System gearbeitet, bei dem (was die Hardware betrifft) null 0xffffffff und 0 eine vollkommen gültige Adresse war
pm100
8

Aber da die Speicheradressierung bei 0 beginnt, ist 0 nicht genauso eine gültige Adresse wie jede andere?

Unter einigen / vielen / allen Betriebssystemen ist die Speicheradresse 0 in irgendeiner Weise speziell. Beispielsweise wird es häufig einem ungültigen / nicht vorhandenen Speicher zugeordnet, was eine Ausnahme verursacht, wenn Sie versuchen, darauf zuzugreifen.

Warum ist stattdessen keine negative Zahl null?

Ich denke, dass Zeigerwerte normalerweise als vorzeichenlose Zahlen behandelt werden: Andernfalls könnte beispielsweise ein 32-Bit-Zeiger nur 2 GB Speicher anstelle von 4 GB adressieren.

ChrisW
quelle
4
Ich habe auf einem Gerät codiert, auf dem die Adresse Null eine gültige Adresse war und kein Speicherschutz vorhanden war. Nullzeiger waren ebenfalls All-Bits-Null; Wenn Sie versehentlich auf einen Nullzeiger geschrieben haben, haben Sie die Betriebssystemeinstellungen, die sich an der Nulladresse befanden, überflogen. Heiterkeit folgte normalerweise nicht.
MM
1
Ja: Auf einer x86-CPU im nicht geschützten Modus ist beispielsweise die Adresse 0 die Interrupt-Vektortabelle .
ChrisW
@ChrisW: Bei x86 im nicht geschützten Modus ist die Adresse Null insbesondere der Interrupt-Vektor zum Teilen durch Null, für den einige Programme möglicherweise durchaus legitime Gründe für das Schreiben haben.
Supercat
Selbst auf Plattformen, auf denen nutzbarer Speicher bei der physischen Adresse Null beginnen würde, könnte eine C-Implementierung leicht entweder die Adresse Null verwenden, um ein Objekt zu halten, dessen Adresse niemals verwendet wird, oder einfach das erste Wort des Speichers unbenutzt lassen. Auf den meisten Plattformen speichert der Vergleich mit Null eine Anweisung im Vergleich zum Vergleich mit irgendetwas anderem, sodass selbst die Verschwendung des ersten Speicherworts billiger wäre als die Verwendung einer Adresse ungleich Null für Null. Beachten Sie, dass es nicht erforderlich ist, dass die Adressen von Dingen, die nicht vom C-Standard abgedeckt werden (z. B. E / A-Ports oder Interrupt-Vektoren), ungleich Null sind, und dass ...
Supercat
... der Systemprozess-Nullzeiger greift anders als jeder andere zu, so dass All-Bit-Null im Allgemeinen eine feine Adresse für "Null" ist, selbst auf Systemen, auf denen der Zugriff auf den physischen Standort Null nützlich und sinnvoll wäre.
Supercat
5

Ich würde vermuten, dass der magische Wert 0 ausgewählt wurde, um einen ungültigen Zeiger zu definieren, da er mit weniger Anweisungen getestet werden konnte. Einige Maschinensprachen setzen beim Laden von Registern automatisch die Null- und Vorzeichenflags gemäß den Daten, sodass Sie mit einem einfachen Lade- und Verzweigungsbefehl auf einen Nullzeiger testen können, ohne einen separaten Vergleichsbefehl auszuführen.

(Die meisten ISAs setzen jedoch nur Flags für ALU-Anweisungen, nicht jedoch für das Laden. Normalerweise erzeugen Sie keine Zeiger über die Berechnung, außer im Compiler, wenn Sie die C- Quelle analysieren . Zumindest benötigen Sie jedoch keine beliebige Konstante für die Zeigerbreite vergleiche mit.)

Auf dem Commodore Pet, Vic20 und C64, die die ersten Maschinen waren, an denen ich gearbeitet habe, wurde der Arbeitsspeicher an Position 0 gestartet, sodass das Lesen und Schreiben mit einem Nullzeiger absolut gültig war, wenn Sie dies wirklich wollten.

KPexEA
quelle
3

Ich denke, es ist nur eine Konvention. Es muss ein Wert vorhanden sein, um einen ungültigen Zeiger zu markieren.

Sie verlieren nur ein Byte Adressraum, das sollte selten ein Problem sein.

Es gibt keine negativen Zeiger. Zeiger sind immer ohne Vorzeichen. Auch wenn sie negativ sein könnten, würde Ihre Konvention bedeuten, dass Sie die Hälfte des Adressraums verlieren.

Axel Gneiting
quelle
Hinweis: Sie verlieren tatsächlich keinen Adressraum. Sie können einen Zeiger auf die Adresse 0 erhalten, indem Sie Folgendes tun : char *p = (char *)1; --p;. Da das Verhalten eines Nullzeigers vom Standard nicht definiert ist, kann dieses System ptatsächlich die Adresse 0 lesen und schreiben, die Adresse erhöhen 1usw.
MM
@MattMcNabb: Eine Implementierung, bei der Adresse Null eine gültige Hardwareadresse ist, kann das Verhalten des Lesens der Adresse Null und des Speicherns dieses Werts in x rechtmäßig definierenchar x = ((char*)0); . Ein solcher Code würde bei jeder Implementierung, die sein Verhalten nicht definiert hat, zu undefiniertem Verhalten führen, aber die Tatsache, dass ein Standard sagt, dass etwas undefiniertes Verhalten ist, verbietet Implementierungen in keiner Weise, ihre eigenen Spezifikationen für das, was er tun wird, anzubieten.
Supercat
@ Supercat ITYM *(char *)0. Das stimmt, aber in meinem Vorschlag muss die Implementierung nicht das Verhalten *(char *)0oder anderer Nullzeigeroperationen definieren .
MM
1
@MattMcNabb: Das Verhalten von char *p = (char*)1; --p;würde nur dann durch den Standard definiert, wenn diese Sequenz ausgeführt worden wäre, nachdem ein Zeiger auf etwas anderes als das erste Byte eines Objekts auf a umgewandelt worden wäre intptr_tund das Ergebnis dieser Umwandlung zufällig den Wert 1 ergab und in diesem speziellen Fall würde das Ergebnis von --peinen Zeiger auf das Byte ergeben, das dem Byte vorausgeht, dessen Zeigerwert bei Umwandlung in intptr_tergeben hätte 1.
Supercat
3

Obwohl C 0 verwendet, um den Nullzeiger darzustellen, beachten Sie, dass der Wert des Zeigers selbst möglicherweise keine Null ist. Die meisten Programmierer verwenden jedoch immer nur Systeme, bei denen der Nullzeiger tatsächlich 0 ist.

Aber warum Null? Nun, es ist eine Adresse, die jedes System teilt. Und oft sind die niedrigen Adressen für Betriebssystemzwecke reserviert, so dass der Wert gut funktioniert und für Anwendungsprogramme nicht zulässig ist. Die versehentliche Zuweisung eines ganzzahligen Werts zu einem Zeiger führt genauso wahrscheinlich zu Null wie alles andere.

George Phillips
quelle
3
Der wahrscheinlichere Grund für all dies ist folgender: Es ist billig, Speicher, der auf Null vorinitialisiert ist, zu verteilen, und es ist praktisch, wenn Werte in diesem Speicher etwas Bedeutendes wie Ganzzahl 0, Gleitkomma 0.0 und Nullzeiger darstellen. Statische Daten in C, die auf Null / Null initialisiert sind, müssen keinen Platz in der ausführbaren Datei belegen und werden beim Laden einem mit Null gefüllten Block zugeordnet. Null kann auch in Maschinensprachen eine Sonderbehandlung erhalten: einfache Nullvergleiche wie "Verzweigen, wenn gleich Null" usw. MIPS hat sogar ein Dummy-Register, das nur eine Nullkonstante ist.
Kaz
2

In der Vergangenheit war der geringe Speicher einer Anwendung mit Systemressourcen belegt. In jenen Tagen wurde Null zum Standardwert für Null.

Dies gilt zwar nicht unbedingt für moderne Systeme, es ist jedoch immer noch eine schlechte Idee, Zeigerwerte auf etwas anderes als die Speicherzuweisung festzulegen, die Sie erhalten haben.

Fred Haslam
quelle
2

In Bezug auf das Argument, einen Zeiger nach dem Löschen nicht auf null zu setzen, damit zukünftige Löschvorgänge "Fehler anzeigen" ...

Wenn Sie sich darüber wirklich große Sorgen machen, besteht ein besserer Ansatz, der garantiert funktioniert, darin, assert () zu nutzen:


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

Dies erfordert einige zusätzliche Eingaben und eine zusätzliche Überprüfung während der Debug-Erstellung, aber es ist sicher, dass Sie das bekommen, was Sie wollen: Beachten Sie, wenn ptr 'zweimal' gelöscht wird. Die in der Kommentardiskussion angegebene Alternative, den Zeiger nicht auf Null zu setzen, damit Sie abstürzen, ist einfach nicht garantiert erfolgreich. Schlimmer noch, im Gegensatz zu den oben genannten, kann es zu einem Absturz (oder viel schlimmer!) Bei einem Benutzer kommen, wenn einer dieser "Fehler" in das Regal gelangt. Schließlich können Sie mit dieser Version das Programm weiter ausführen, um zu sehen, was tatsächlich passiert.

Mir ist klar, dass dies die gestellte Frage nicht beantwortet, aber ich war besorgt, dass jemand, der die Kommentare liest, zu dem Schluss kommen könnte, dass es als "gute Praxis" angesehen wird, Zeiger NICHT auf 0 zu setzen, wenn es möglich ist, dass sie an free () oder gesendet werden zweimal löschen. In diesen wenigen Fällen, in denen es möglich ist, ist es NIE eine gute Praxis, undefiniertes Verhalten als Debugging-Tool zu verwenden. Niemand, der jemals einen Fehler suchen musste, der letztendlich durch das Löschen eines ungültigen Zeigers verursacht wurde, würde dies vorschlagen. Diese Art von Fehlern dauert Stunden, um sie aufzuspüren, und wirkt sich fast immer auf eine völlig unerwartete Weise auf das Programm aus, die schwer bis unmöglich auf das ursprüngliche Problem zurückzuführen ist.

Edward Strange
quelle
2

Ein wichtiger Grund, warum viele Betriebssysteme All-Bits-Null für die Nullzeigerdarstellung verwenden, ist, dass dies bedeutet, dass memset(struct_with_pointers, 0, sizeof struct_with_pointers)alle darin enthaltenen Zeiger struct_with_pointersauf Nullzeiger gesetzt werden. Dies wird vom C-Standard nicht garantiert, aber viele, viele Programme gehen davon aus.

zwol
quelle
1

In einer der alten DEC-Maschinen (PDP-8, glaube ich) würde die C-Laufzeit die erste Speicherseite durch den Speicher schützen, sodass bei jedem Versuch, auf den Speicher in diesem Block zuzugreifen, eine Ausnahme ausgelöst wird.

Paul Tomblin
quelle
Der PDP-8 hatte keinen C-Compiler. Der PDP-11 hatte keinen Speicherschutz und der VAX war berüchtigt dafür, dass er stillschweigend 0 bis NULL-Zeiger-Dereferenzen zurückgab. Ich bin mir nicht sicher, auf welche Maschine sich das bezieht.
Fuz
1

Die Wahl des Sentinel-Werts ist willkürlich, und dies wird in der nächsten Version von C ++ (informell bekannt als "C ++ 0x", wahrscheinlich in Zukunft als ISO C ++ 2011 bekannt) mit der Einführung des Schlüsselwort nullptrzur Darstellung eines Nullwertzeigers. In C ++ kann ein Wert von 0 als Initialisierungsausdruck für jeden POD und für jedes Objekt mit einem Standardkonstruktor verwendet werden. Dies hat die besondere Bedeutung, dass bei einer Zeigerinitialisierung der Sentinel-Wert zugewiesen wird. Warum kein negativer Wert gewählt wurde, liegt normalerweise zwischen 0 und 2 N.-1 für einen Wert N. Mit anderen Worten, Adressen werden normalerweise als vorzeichenlose Werte behandelt. Wenn der Maximalwert als Sentinel-Wert verwendet würde, müsste er je nach Speichergröße von System zu System variieren, während 0 immer eine darstellbare Adresse ist. Es wird auch aus historischen Gründen verwendet, da die Speicheradresse 0 in Programmen normalerweise unbrauchbar war und heutzutage in den meisten Betriebssystemen Teile des Kernels in die unteren Speicherseiten geladen sind und solche Seiten normalerweise so geschützt sind, dass wenn Berühren (Dereferenzieren) durch ein Programm (Speichern des Kernels) führt zu einem Fehler.

Michael Aaron Safyan
quelle
1

Es muss einen Wert haben. Offensichtlich möchten Sie nicht auf Werte treten, die der Benutzer möglicherweise rechtmäßig verwenden möchte. Ich würde spekulieren, dass es einen gewissen Sinn macht, Null als nicht initialisierten Zeigerwert zu interpretieren, da die C-Laufzeit das BSS-Segment für nullinitialisierte Daten bereitstellt.

JustJeff
quelle
0

In seltenen Fällen können Sie mit einem Betriebssystem an Adresse 0 schreiben. nämlich IDTs, Seitentabellen usw. (Die Tabellen müssen sich im RAM befinden, und es ist einfacher, sie unten zu platzieren, als zu versuchen, festzustellen, wo sich die Oberseite des RAM befindet.) Und kein Betriebssystem, das bei Verstand ist, lässt Sie zu Bearbeiten Sie Systemtabellen wohl oder übel.

Dies war K & R vielleicht nicht in den Sinn gekommen, als sie C gemacht haben, aber es (zusammen mit der Tatsache, dass 0 == null ziemlich leicht zu merken ist) macht 0 zu einer beliebten Wahl.

cHao
quelle
Dies ist im geschützten Modus nicht wahr, und in der Tat, auf bestimmten Linux - Konfigurationen Sie können auf virtuelle Adresse schreiben 0
L̳o̳̳n̳̳g̳̳p̳o̳̳k̳̳e̳̳
0

Der Wert 0ist ein spezieller Wert, der in bestimmten Ausdrücken verschiedene Bedeutungen annimmt. Im Fall von Zeigern wird es, wie schon oft erwähnt, wahrscheinlich verwendet, weil es zu der Zeit die bequemste Art war, "hier den Standard-Sentinel-Wert einfügen" zu sagen. Als konstanter Ausdruck hat er im Kontext eines Zeigerausdrucks nicht die gleiche Bedeutung wie bitweise Null (dh alle auf Null gesetzten Bits). In C ++ gibt es mehrere Typen, die keine bitweise Nulldarstellung haben, NULLz. B. Zeigerelement und Zeiger auf Elementfunktion.

Zum Glück hat C ++ 0x ein neues Schlüsselwort für "Ausdruck, der einen bekannten ungültigen Zeiger bedeutet, der für integrale Ausdrücke nicht auch bitweise Null zugeordnet ist" : nullptr. Obwohl es einige Systeme gibt, auf die Sie mit C ++ abzielen können und die eine Dereferenzierung der Adresse 0 ohne Barfing ermöglichen, sollten Sie sich vor Programmierern in Acht nehmen.

MSN
quelle
0

In diesem Thread gibt es bereits viele gute Antworten. Es gibt wahrscheinlich viele verschiedene Gründe, den Wert 0für Nullzeiger zu bevorzugen , aber ich werde zwei weitere hinzufügen:

  • In C ++ wird ein Zeiger durch Nullinitialisierung auf Null gesetzt.
  • Auf vielen Prozessoren ist es effizienter, einen Wert auf 0 zu setzen oder zu testen, ob er gleich / ungleich 0 ist, als für jede andere Konstante.
Mark Ransom
quelle
0

Dies hängt von der Implementierung von Zeigern in C / C ++ ab. Es gibt keinen bestimmten Grund, warum NULL bei Zuweisungen zu einem Zeiger gleichwertig ist.

Nagabhushan Baddi
quelle
-1

Dafür gibt es historische Gründe, aber es gibt auch Optimierungsgründe dafür.

Es ist üblich, dass das Betriebssystem einen Prozess mit auf 0 initialisierten Speicherseiten bereitstellt. Wenn ein Programm einen Teil dieser Speicherseite als Zeiger interpretieren möchte, ist es 0, sodass das Programm leicht feststellen kann, dass dieser Zeiger ist nicht initialisiert. (Dies funktioniert nicht so gut, wenn es auf nicht initialisierte Flash-Seiten angewendet wird.)

Ein weiterer Grund ist, dass es auf vielen Prozessoren sehr einfach ist, die Äquivalenz eines Werts mit 0 zu testen. Manchmal handelt es sich um einen kostenlosen Vergleich, bei dem keine zusätzlichen Anweisungen erforderlich sind, und der normalerweise durchgeführt werden kann, ohne dass in einem anderen Register oder ein Wert von Null angegeben werden muss als Literal im Anweisungsstrom zu vergleichen.

Die billigen Vergleiche für die meisten Prozessoren sind die Vorzeichen kleiner als 0 und gleich 0. (Vorzeichen größer als 0 und ungleich 0 werden von beiden impliziert)

Da 1 Wert von allen möglichen Werten als schlecht oder nicht initialisiert reserviert werden muss, können Sie ihn genauso gut zu dem Wert machen, der den billigsten Test für die Gleichwertigkeit mit dem schlechten Wert hat. Dies gilt auch für mit '\ 0' abgeschlossene Zeichenfolgen.

Wenn Sie versuchen würden, für diesen Zweck mehr oder weniger als 0 zu verwenden, würden Sie Ihren Adressbereich in zwei Hälften teilen.

nategoose
quelle
-2

Die Konstante 0verwendet wird , statt , NULLweil C vor von einigen cavemen Billionen von Jahren gemacht wurde, NULL, NIL, ZIP, oder NADDAhätte alles gemacht viel mehr Sinn als 0.

Aber da die Speicheradressierung bei 0 beginnt, ist 0 nicht genauso eine gültige Adresse wie jede andere?

Tatsächlich. Obwohl viele Betriebssysteme es Ihnen nicht erlauben, irgendetwas an der Adresse Null zuzuordnen, selbst in einem virtuellen Adressraum (die Leute erkannten, dass C eine unsichere Sprache ist, und weil Nullzeiger-Dereferenzierungsfehler sehr häufig sind, haben Sie beschlossen, sie zu beheben, indem Sie das nicht zulassen Userspace-Code, der Seite 0 zugeordnet werden soll. Wenn Sie also einen Rückruf aufrufen, der Rückrufzeiger jedoch NULL ist, wird am Ende kein beliebiger Code ausgeführt.

Wie kann 0 für den Umgang mit Nullzeigern verwendet werden, wenn dies der Fall ist?

Da die 0Verwendung im Vergleich zu einem Zeiger durch einen implementierungsspezifischen Wert ersetzt wird, der der Rückgabewert von malloc bei einem Malloc-Fehler ist.

Warum ist stattdessen keine negative Zahl null?

Das wäre noch verwirrender.

L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳
quelle
Ihr Standpunkt zu "Höhlenmenschen" usw. liegt wahrscheinlich an der Wurzel, obwohl ich denke, dass die Besonderheiten unterschiedlich sind. Die frühesten Formen dessen, was sich zu C entwickelte, wurden entwickelt, um auf einer bestimmten Architektur zu laufen, bei der a intnicht nur die gleiche Größe wie ein Zeiger hatte - in vielen Kontexten konnten ein intund ein Zeiger austauschbar verwendet werden. Wenn eine Routine einen Zeiger erwartete und eine in einer ganzen Zahl 57 übergeben wurde, würde die Routine eine Adresse mit demselben Bitmuster wie die Zahl 57 verwenden. Auf diesen bestimmten Maschinen war das Bitmuster zur Bezeichnung eines Nullzeigers 0, so dass eine int 0 übergeben wurde würde einen Nullzeiger übergeben.
Supercat
Seit dieser Zeit hat sich das C so weiterentwickelt, dass es zum Schreiben von Programmen für eine Vielzahl anderer Maschinen mit unterschiedlichen Darstellungen von Zahlen und Zeigern verwendet werden kann. Während numerische Konstanten ungleich Null selten als Zeiger verwendet wurden, wurden konstante numerische Nullen häufig verwendet, um Nullzeiger darzustellen. Das Verbot einer solchen Verwendung hätte den vorhandenen Code beschädigt, sodass von Compilern erwartet wurde, dass sie eine numerische Null in das übersetzen, was die Implementierung zur Darstellung eines Nullzeigers verwendet.
Supercat
-4

( Bitte lesen Sie diesen Absatz, bevor Sie den Beitrag lesen.Ich bitte jeden, der daran interessiert ist, diesen Beitrag zu lesen, zu versuchen, ihn sorgfältig zu lesen, und natürlich nicht abzustimmen, bis Sie ihn vollständig verstanden haben, danke. )

Es ist jetzt ein Community-Wiki. Wenn jemand mit einem der Konzepte nicht einverstanden ist, ändern Sie es bitte mit einer klaren und detaillierten Erklärung, was falsch ist und warum, und wenn möglich, zitieren Sie Quellen oder liefern Sie Beweise, die reproduziert werden können.

Antworten

Hier sind einige andere Gründe, die die zugrunde liegenden Faktoren für NULL == 0 sein könnten

  1. Die Tatsache, dass Null falsch ist, kann man also direkt if(!my_ptr)statt tun if(my_ptr==NULL).
  2. Die Tatsache, dass nicht initiierte globale Ganzzahlen standardmäßig auf alle Nullen initialisiert werden und als solcher ein Zeiger aller Nullen als nicht initialisiert betrachtet wird.

Hier möchte ich ein Wort zu anderen Antworten sagen

Nicht wegen syntaktischen Zuckers

Zu sagen, dass NULL wegen syntaktischen Zuckers Null ist, macht nicht allzu viel Sinn. Wenn ja, warum nicht den Index 0 eines Arrays verwenden, um seine Länge zu halten?

Tatsächlich ist C die Sprache, die der internen Implementierung am ähnlichsten ist. Ist es sinnvoll zu sagen, dass C nur wegen syntaktischen Zuckers Null gewählt hat? Sie würden lieber ein Schlüsselwort null angeben (wie es viele andere Sprachen tun), als null auf NULL abzubilden!

Obwohl es sich heute möglicherweise nur um syntaktischen Zucker handelt, ist klar, dass die ursprüngliche Absicht der Entwickler der C-Sprache nicht syntaktischer Zucker war, wie ich weiter zeigen werde.

1) Die Spezifikation

Zwar spricht die C-Spezifikation von der Konstanten 0 als Nullzeiger (Abschnitt 6.3.2.3) und definiert auch NULL als implementierungsdefiniert (Abschnitt 7.19 in der C11-Spezifikation und 7.17 in der C99-Spezifikation) Tatsache bleibt, dass in dem von den Erfindern von C verfassten Buch "The C Programming Language" in Abschnitt 5.4 Folgendes angegeben ist:

C garantiert, dass Null niemals eine gültige Adresse für Daten ist, sodass ein Rückgabewert von Null verwendet werden kann, um ein abnormales Ereignis zu signalisieren, in diesem Fall kein Leerzeichen.

Zeiger und ganze Zahlen sind nicht austauschbar, Null ist die einzige Ausnahme: Die Konstante Null kann einem Zeiger zugewiesen werden, und ein Zeiger kann mit der Konstanten Null verglichen werden. Die symbolische Konstante NULL wird häufig anstelle von Null als Mnemonik verwendet, um deutlicher anzuzeigen, dass dies ein spezieller Wert für einen Zeiger ist. NULL ist definiert in. Wir werden fortan NULL verwenden.

Wie man sehen kann (aus den Worten "Nulladresse"), war zumindest die ursprüngliche Absicht der Autoren von C die Adresse Null und nicht die Konstante Null, außerdem geht aus diesem Auszug hervor, dass der Grund, warum die Spezifikation von der spricht Die Konstante Null soll wahrscheinlich keinen Ausdruck ausschließen, der als Null ausgewertet wird, sondern stattdessen die Ganzzahlkonstante Null als einzige Ganzzahlkonstante einschließen, die in einem Zeigerkontext ohne Umwandlung verwendet werden darf.

2) Zusammenfassung

Während die Spezifikation nicht explizit besagt, dass eine Nulladresse anders als die Nullkonstante behandelt werden kann, sagt sie dies nicht, und die Tatsache, dass beim Umgang mit der Nullzeigerkonstante nicht behauptet wird, dass sie als solche definiert ist Wenn die von NULL definierte Konstante stattdessen behauptet, sie sei Null, zeigt dies, dass möglicherweise ein Unterschied zwischen der Nullkonstante und der Nulladresse besteht.

(Wenn dies jedoch der Fall ist, frage ich mich nur, warum NULL implementierungsdefiniert ist, da in einem solchen Fall NULL auch die Konstante Null sein kann, da der Compiler ohnehin alle Nullkonstanten in die tatsächlich implementierte Implementierung NULL konvertieren muss.)

Ich sehe dies jedoch nicht in der realen Aktion, und auf den allgemeinen Plattformen werden die Adresse Null und die Konstante Null gleich behandelt und werfen die gleiche Fehlermeldung aus.

Tatsache ist außerdem, dass die heutigen Betriebssysteme tatsächlich die gesamte erste Seite (Bereich 0x0000 bis 0xFFFF) reservieren, um den Zugriff auf die Nulladresse aufgrund des NULL-Zeigers von C zu verhindern (siehe http://en.wikipedia.org/wiki/). Zero_page sowie "Windows Via C / C ++ von Jeffrey Richter und Christophe Nasarre (herausgegeben von Microsoft Press)").

Daher würde ich jeden, der behauptet, es tatsächlich in Aktion gesehen zu haben, bitten, die Plattform und den Compiler sowie den genauen Code anzugeben, den er tatsächlich gemacht hat (obwohl aufgrund der vagen Definition in der Spezifikation [wie ich gezeigt habe] jeder Compiler und die Plattform ist frei zu tun, was er will).

Es scheint jedoch, dass die Autoren von C dies nicht im Sinn hatten und von der "Null-Adresse" sprachen und dass "C garantiert, dass es sich nie um eine gültige Adresse handelt" sowie "NULL ist nur eine" Mnemonik ", was deutlich zeigt, dass die ursprüngliche Absicht nicht" syntaktischer Zucker "war.

Nicht wegen des Betriebssystems

Außerdem wird behauptet, dass das Betriebssystem aus mehreren Gründen den Zugriff auf die Adresse Null verweigert:

1) Als C geschrieben wurde, gab es keine solche Einschränkung, wie man auf dieser Wikipage http://en.wikipedia.org/wiki/Zero_page sehen kann .

2) Tatsache ist, dass C-Compiler auf die Speicheradresse Null zugegriffen haben.

Dies scheint die Tatsache aus dem folgenden Artikel von BellLabs ( http://www.cs.bell-labs.com/who/dmr/primevalC.html ) zu sein.

Die beiden Compiler unterscheiden sich in den Details darin, wie sie damit umgehen. Im vorherigen Fall wird der Start durch Benennen einer Funktion gefunden. Im späteren Fall wird der Start einfach als 0 angenommen. Dies zeigt an, dass der erste Compiler geschrieben wurde, bevor wir eine Maschine mit Speicherzuordnung hatten, sodass der Ursprung des Programms nicht an Position 0 lag, während zum Zeitpunkt des zweiten Wir hatten einen PDP-11, der Mapping lieferte.

(Tatsächlich liegt der Grund für die Einschränkung des Zugriffs auf die Nulladresse ab heute (wie ich oben in Wikipedia und Microsoft Press zitiert habe) in den NULL-Zeigern von C! Am Ende stellt sich heraus, dass es umgekehrt ist!)

3) Denken Sie daran, dass C auch zum Schreiben von Betriebssystemen und sogar von C-Compilern verwendet wird!

Tatsächlich wurde C entwickelt, um das UNIX-Betriebssystem damit zu schreiben, und als solches scheint es kein Grund zu sein, sich von der Adresse Null zu beschränken.

(Hardware) Erläuterung, wie Computer (physisch) auf die Adresse Null zugreifen können

Es gibt noch einen weiteren Punkt, den ich hier erläutern möchte: Wie ist es überhaupt möglich, auf die Adresse Null zu verweisen?

Denken Sie eine Sekunde darüber nach, die Adressen werden vom Prozessor abgerufen und dann als Spannungen auf dem Speicherbus gesendet, der dann vom Speichersystem verwendet wird, um zur tatsächlichen Adresse zu gelangen, und dennoch bedeutet eine Adresse von Null keine Spannung Wie greift die physische Hardware des Speichersystems auf die Adresse Null zu?

Die Antwort scheint zu sein, dass die Adresse Null die Standardeinstellung ist, und mit anderen Worten, die Adresse Null ist für das Speichersystem immer zugänglich, wenn der Speicherbus vollständig ausgeschaltet ist, und als solche jede Anforderung zum Lesen oder Schreiben, ohne eine tatsächliche Adresse anzugeben (welche ist der Fall bei Adresse Null) greift automatisch auf Adresse Null zu.

yo hal
quelle
1
Ich habe Sie nicht abgelehnt, aber Ihr Beitrag weist einige sachliche Ungenauigkeiten auf, z. Auf diesen physischen Speicher bei Offset 0 kann nicht zugegriffen werden (da alle Schalter ausgeschaltet sind? Wirklich?), 0 und die Konstante 0 sind austauschbar (möglicherweise nicht) und andere.
Hasturkun
In Bezug auf 0 und die Konstante Null ist dies das, was das ursprüngliche Buch sagt, und was die tatsächlichen Tests zeigen, haben Sie einen echten Unterschied zwischen den beiden festgestellt? Wenn ja, welcher Compiler und welche Plattform? Viele Antworten deuten darauf hin, dass es einen Unterschied gibt, den ich nicht gefunden habe, und sie haben keinen Hinweis darauf, dass es einen Unterschied gibt. In der Tat laut en.wikipedia.org/wiki/Zero_page und auch "Windows über C / C ++ von Jeffrey Richter und Christophe Nasarre (veröffentlicht von Microsoft Press)" die gesamte erste Seite! ist in modernen Computern nur geschützt, um Null zu verhindern (tatsächlich mehr als ein Byte zu verschwenden!)
yoel halb
Natürlich wird das Bitmuster der Adresse verwendet, um auszuwählen, was gelesen wird. Dies ist im Allgemeinen der Fall. Jedenfalls möchte ich nicht mit Ihnen streiten, ich habe nur darauf hingewiesen, warum Sie möglicherweise abgelehnt wurden.
Hasturkun
Ich stimme Ihren Behauptungen nicht zu. Ich bin auch nicht daran interessiert, diese Diskussion fortzusetzen.
Hasturkun
6
Hardware-Anspruch ist Unsinn. Um die Adresse Null zu lesen, fahren Sie! Chip Select niedrig ,! RAS hoch ,! CAS niedrig ,! WE hoch und alle Adressleitungen niedrig. Wenn der Bus ausgeschaltet ist, ist! CS hoch.
MSalters