Bitte fügen Sie der Erklärung ein Beispiel bei.
c++
c
pointers
dereference
asir
quelle
quelle
int *p;
würde einen Zeiger auf eine ganze Zahl definieren und*p
diesen Zeiger dereferenzieren, was bedeutet, dass er tatsächlich die Daten abruft, auf die p zeigt.Antworten:
Überprüfung der grundlegenden Terminologie
Es ist normalerweise gut genug - es sei denn, Sie programmieren Assembly -, sich einen Zeiger vorzustellen, der eine numerische Speicheradresse enthält, wobei 1 auf das zweite Byte im Speicher des Prozesses verweist, 2 auf das dritte, 3 auf das vierte und so weiter.
Wenn Sie auf die Daten / Werte im Speicher zugreifen möchten, auf die der Zeiger zeigt - den Inhalt der Adresse mit diesem numerischen Index -, dereferenzieren Sie den Zeiger.
Verschiedene Computersprachen haben unterschiedliche Notationen, um dem Compiler oder Interpreter mitzuteilen, dass Sie jetzt am (aktuellen) Wert des Objekts interessiert sind - ich konzentriere mich unten auf C und C ++.
Ein Zeigerszenario
Betrachten Sie in C einen Zeiger wie den
p
folgenden ...... vier Bytes mit den numerischen Werten, die zum Codieren der Buchstaben 'a', 'b', 'c' und eines 0-Bytes zum Bezeichnen des Endes der Textdaten verwendet werden, werden irgendwo im Speicher gespeichert und die numerische Adresse davon Daten werden in gespeichert
p
. Auf diese Weise codiert C Text im Speicher und wird als ASCIIZ bezeichnet .Wenn sich das Zeichenfolgenliteral beispielsweise an der Adresse 0x1000 und
p
ein 32-Bit-Zeiger an der Adresse 0x2000 befindet, lautet der Speicherinhalt wie folgt:Beachten Sie, dass es für die Adresse 0x1000 keinen Variablennamen / Bezeichner gibt. Wir können jedoch indirekt auf das Zeichenfolgenliteral verweisen, indem wir einen Zeiger verwenden, in dem die Adresse gespeichert ist :
p
.Dereferenzieren des Zeigers
Um auf die Zeichen zu verweisen, auf die
p
verwiesen wird, dereferenzieren wirp
mit einer dieser Notationen (wiederum für C):Sie können auch Zeiger durch die Daten verschieben, auf die verwiesen wird, und sie anschließend dereferenzieren:
Wenn Sie Daten haben, in die geschrieben werden kann, können Sie Folgendes tun:
Oben müssen Sie zur Kompilierungszeit gewusst haben, dass Sie eine aufgerufene Variable benötigen
x
, und der Code fordert den Compiler auf, festzulegen , wo sie gespeichert werden soll, um sicherzustellen, dass die Adresse über verfügbar ist&x
.Dereferenzieren und Zugreifen auf ein Strukturdatenelement
Wenn Sie in C eine Variable haben, die ein Zeiger auf eine Struktur mit Datenelementen ist, können Sie mit dem
->
Dereferenzierungsoperator auf diese Elemente zugreifen :Multi-Byte-Datentypen
Um einen Zeiger zu verwenden, benötigt ein Computerprogramm auch einen Einblick in den Datentyp, auf den verwiesen wird. Wenn dieser Datentyp mehr als ein Byte zur Darstellung benötigt, zeigt der Zeiger normalerweise auf das Byte mit der niedrigsten Nummer in den Daten.
Betrachten wir also ein etwas komplexeres Beispiel:
Zeiger auf dynamisch zugewiesenen Speicher
Manchmal wissen Sie nicht, wie viel Speicher Sie benötigen, bis Ihr Programm ausgeführt wird und Sie sehen, welche Daten darauf geworfen werden. Dann können Sie mithilfe von Speicher dynamisch Speicher zuweisen
malloc
. Es ist üblich, die Adresse in einem Zeiger zu speichern ...In C ++ erfolgt die Speicherzuweisung normalerweise mit dem
new
Operator und die Freigabe mitdelete
:Siehe auch C ++ - Smartpointer unten.
Adressen verlieren und verlieren
Oft ist ein Zeiger der einzige Hinweis darauf, wo sich Daten oder Puffer im Speicher befinden. Wenn die fortlaufende Verwendung dieser Daten / Puffer oder die Fähigkeit zum Aufrufen
free()
oderdelete
Vermeiden eines Speicherverlusts erforderlich ist, muss der Programmierer eine Kopie des Zeigers bearbeiten ...... oder die Umkehrung von Änderungen sorgfältig orchestrieren ...
C ++ Smart Pointer
In C ++ empfiehlt es sich, Smart Pointer- Objekte zum Speichern und Verwalten der Zeiger zu verwenden und diese automatisch freizugeben, wenn die Destruktoren der Smart Pointer ausgeführt werden. Seit C ++ 11 bietet die Standardbibliothek zwei,
unique_ptr
wenn es einen einzelnen Eigentümer für ein zugewiesenes Objekt gibt ...... und
shared_ptr
für den Aktienbesitz (unter Verwendung der Referenzzählung ) ...Nullzeiger
In C
NULL
und0
- und zusätzlich in C ++nullptr
- kann angegeben werden, dass ein Zeiger derzeit nicht die Speicheradresse einer Variablen enthält und nicht dereferenziert oder in der Zeigerarithmetik verwendet werden sollte. Zum Beispiel:In C und C ++, ebenso wie integrierte numerische Typen standardmäßig nicht unbedingt
0
, nochbools
zufalse
, Zeiger sind nicht immer aufNULL
. Alle diese sind auf 0 / false / NULL gesetzt , wenn siestatic
Variablen oder (nur C ++) direkt oder indirekt Elementvariablen von statischen Objekten oder ihre Basen oder Null Initialisierung durchlaufen (zBnew T();
undnew T(x, y, z);
führt Null-Initialisierung auf T-Mitgliedern einschließlich Zeiger, währendnew T;
nicht).Wenn Sie und einem Zeiger zuweisen
0
, werden die Bits im Zeiger nicht unbedingt alle zurückgesetzt: Der Zeiger enthält möglicherweise nicht "0" auf Hardwareebene oder verweist auf die Adresse 0 in Ihrem virtuellen Adressraum. Der Compiler wird zum Speichern von etwas erlaubt sonst da , wenn sie Grund zu sein , hat aber was immer es tut - wenn Sie zusammen kommen und vergleichen Sie den Zeiger auf , , oder ein anderer Zeiger, der alle diejenigen, die Vergleichs muss wie erwartet zugewiesen wurde. Unterhalb des Quellcodes auf Compilerebene ist "NULL" in den Sprachen C und C ++ möglicherweise etwas "magisch" ...NULL
nullptr
0
NULL
nullptr
Mehr über Speicheradressen und warum Sie es wahrscheinlich nicht wissen müssen
Streng genommen speichern initialisierte Zeiger ein Bitmuster, das entweder eine
NULL
oder eine (häufig virtuelle ) Speicheradresse identifiziert .Der einfache Fall ist, dass dies ein numerischer Versatz im gesamten virtuellen Adressraum des Prozesses ist. In komplexeren Fällen kann der Zeiger relativ zu einem bestimmten Speicherbereich sein, den die CPU basierend auf CPU- "Segment" -Registern oder einer Art von Segment-ID auswählen kann, die im Bitmuster codiert ist, und / oder abhängig von der Maschinencode-Anweisungen unter Verwendung der Adresse.
Beispielsweise kann eine
int*
ordnungsgemäß initialisierte Datei, die auf eineint
Variable verweist, nach dem Umwandeln in einenfloat*
Zugriffsspeicher im "GPU" -Speicher ganz anders sein als der Speicher, in dem sich dieint
Variable befindet, und nach dem Umwandeln und Verwenden als Funktionszeiger möglicherweise weiter verweisen Opcodes für bestimmte Speicher-Haltemaschinen für das Programm (mit dem numerischen Wert desint*
effektiv zufälligen, ungültigen Zeigers innerhalb dieser anderen Speicherbereiche).3GL-Programmiersprachen wie C und C ++ neigen dazu, diese Komplexität zu verbergen, so dass:
Wenn der Compiler Ihnen einen Zeiger auf eine Variable oder Funktion gibt, können Sie diese frei dereferenzieren (solange die Variable nicht zerstört / freigegeben wurde) und es ist das Problem des Compilers, ob z. B. ein bestimmtes CPU-Segmentregister im Voraus wiederhergestellt werden muss oder a Es wird eine bestimmte Anweisung für den Maschinencode verwendet
Wenn Sie einen Zeiger auf ein Element in einem Array erhalten, können Sie mithilfe der Zeigerarithmetik eine beliebige Stelle im Array verschieben oder sogar eine Adresse nach dem Ende des Arrays bilden, die mit anderen Zeigern auf Elemente verglichen werden kann im Array (oder die in ähnlicher Weise durch Zeigerarithmetik auf denselben Wert nach dem Ende verschoben wurden); Auch in C und C ++ muss der Compiler sicherstellen, dass dies "einfach funktioniert".
Bestimmte Betriebssystemfunktionen, z. B. die Zuordnung von gemeinsam genutztem Speicher, können Ihnen Zeiger geben, und sie funktionieren "nur" innerhalb des für sie sinnvollen Adressbereichs
Versuche, legale Zeiger über diese Grenzen hinaus zu verschieben oder beliebige Zahlen in Zeiger umzuwandeln oder Zeiger zu verwenden, die in nicht verwandte Typen umgewandelt wurden, weisen normalerweise ein undefiniertes Verhalten auf . Sie sollten daher in Bibliotheken und Anwendungen höherer Ebenen vermieden werden, aber Code für Betriebssysteme, Gerätetreiber usw. Möglicherweise müssen Sie sich auf ein Verhalten verlassen, das vom C- oder C ++ - Standard nicht definiert wurde und das dennoch durch die spezifische Implementierung oder Hardware gut definiert ist.
quelle
p[1]
und*(p + 1)
identisch ? Das heißt, erzeugtp[1]
und*(p + 1)
generiert die gleichen Anweisungen?p
lautet die Basisadresse von nur 2000: Wenn Sie einen anderen Zeigerp
darauf hätten, müssten 2000 in seinen vier oder acht Bytes gespeichert werden. Ich hoffe, das hilft! Prost.u
ein Array enthältarr
, erkennen sowohl gcc als auch clang, dass der lvalueu.arr[i]
möglicherweise auf denselben Speicher wie andere Union-Mitglieder zugreift, erkennen jedoch nicht, dass lvalue dies*(u.arr+i)
möglicherweise tut. Ich bin nicht sicher, ob die Autoren dieser Compiler der Meinung sind, dass der letztere UB aufruft oder dass der erstere UB aufruft, aber sie sollten es trotzdem sinnvoll verarbeiten, aber sie sehen die beiden Ausdrücke eindeutig als unterschiedlich an.Das Dereferenzieren eines Zeigers bedeutet, dass der Wert abgerufen wird, der an dem Speicherort gespeichert ist, auf den der Zeiger zeigt. Der Operator * wird dazu verwendet und als Dereferenzierungsoperator bezeichnet.
quelle
[]
auch einen Zeiger dereferenziert (a[b]
definiert als bedeutet*(a + b)
).Ein Zeiger ist eine "Referenz" auf einen Wert. Ähnlich wie eine Bibliotheksrufnummer eine Referenz auf ein Buch ist. Durch "Dereferenzieren" der Rufnummer wird das Buch physisch durchlaufen und abgerufen.
Wenn das Buch nicht da ist, fängt der Bibliothekar an zu schreien, schließt die Bibliothek und ein paar Leute sind bereit, die Ursache zu untersuchen, warum eine Person ein Buch findet, das nicht da ist.
quelle
In einfachen Worten bedeutet Dereferenzierung den Zugriff auf den Wert von einem bestimmten Speicherort aus, auf den dieser Zeiger zeigt.
quelle
Code und Erklärung aus den Zeigergrundlagen :
quelle
Ich denke, alle vorherigen Antworten sind falsch, da sie besagen, dass Dereferenzierung den Zugriff auf den tatsächlichen Wert bedeutet. Wikipedia gibt stattdessen die richtige Definition an: https://en.wikipedia.org/wiki/Dereference_operator
Das heißt, wir können den Zeiger dereferenzieren, ohne jemals auf den Wert zuzugreifen, auf den er zeigt. Zum Beispiel:
Wir haben den NULL-Zeiger dereferenziert, ohne auf seinen Wert zuzugreifen. Oder wir könnten tun:
Wieder Dereferenzierung, aber niemals Zugriff auf den Wert. Ein solcher Code stürzt NICHT ab: Der Absturz tritt auf, wenn Sie tatsächlich mit einem ungültigen Zeiger auf die Daten zugreifen . Leider ist das Dereferenzieren eines ungültigen Zeigers gemäß dem Standard ein undefiniertes Verhalten (mit wenigen Ausnahmen), selbst wenn Sie nicht versuchen, die tatsächlichen Daten zu berühren.
Kurz gesagt: Dereferenzieren des Zeigers bedeutet, den Dereferenzierungsoperator darauf anzuwenden. Dieser Operator gibt nur einen l-Wert für Ihre zukünftige Verwendung zurück.
quelle
*p;
verursacht undefiniertes Verhalten. Obwohl Sie Recht haben, dass die Dereferenzierung nicht auf den Wert an sich zugreift ,*p;
greift der Code auf den Wert zu.