Bitte fügen Sie der Erklärung ein Beispiel bei.
Antworten
Zu viele Anzeigen?Wiederholung der grundlegenden Terminologie
Es ist in der Regel gut genug - es sei denn, Sie programmieren Assembler - um eine Zeiger mit einer numerischen Speicheradresse, wobei sich 1 auf das zweite Byte im Speicher des Prozesses bezieht, 2 auf das dritte, 3 auf das vierte und so weiter....
- Was ist mit der 0 und dem ersten Byte passiert? Nun, dazu kommen wir später - siehe Null-Zeiger unten.
- Eine genauere Definition dessen, was Zeiger speichern und wie Speicher und Adressen zusammenhängen, finden Sie unter "Mehr über Speicheradressen, und warum Sie das wahrscheinlich nicht wissen müssen" am Ende dieser Antwort.
Wenn Sie auf die Daten/Werte im Speicher zugreifen wollen, auf die der Zeiger zeigt - den Inhalt der Adresse mit diesem numerischen Index -, dann müssen Sie Dereferenzierung den Zeiger.
Verschiedene Computersprachen haben unterschiedliche Notationen, um dem Compiler oder Interpreter mitzuteilen, dass Sie jetzt am (aktuellen) Wert des Objekts, auf das gezeigt wird, interessiert sind - ich konzentriere mich im Folgenden auf C und C++.
Ein Zeiger-Szenario
Nehmen wir an, in C wird ein Zeiger wie p
unten...
const char* p = "abc";
...vier Bytes mit den numerischen Werten, die zur Kodierung der Buchstaben "a", "b" und "c" verwendet werden, und ein 0-Byte, das das Ende der Textdaten kennzeichnet, irgendwo im Speicher abgelegt werden und die numerische Adresse dieser Daten ist gespeichert in p
. Diese Art und Weise, wie C Text im Speicher kodiert, ist bekannt als ASCIIZ .
Wenn sich das String-Literal beispielsweise an der Adresse 0x1000 befand und p
einen 32-Bit-Zeiger an 0x2000, würde der Speicherinhalt sein:
Memory Address (hex) Variable name Contents
1000 'a' == 97 (ASCII)
1001 'b' == 98
1002 'c' == 99
1003 0
...
2000-2003 p 1000 hex
Beachten Sie, dass es keinen Variablennamen/Kennzeichen für die Adresse 0x1000 gibt, aber wir können indirekt auf das Stringliteral verweisen, indem wir einen Zeiger verwenden, der seine Adresse speichert: p
.
Dereferenzierung des Zeigers
Zum Verweisen auf die Zeichen p
verweist, dereferenzieren wir p
unter Verwendung einer dieser Notationen (wiederum für C):
assert(*p == 'a'); // The first character at address p will be 'a'
assert(p[1] == 'b'); // p[1] actually dereferences a pointer created by adding
// p and 1 times the size of the things to which p points:
// In this case they're char which are 1 byte in C...
assert(*(p + 1) == 'b'); // Another notation for p[1]
Sie können auch Zeiger durch die Daten, auf die gezeigt wird, bewegen und sie dabei derefenzieren:
++p; // Increment p so it's now 0x1001
assert(*p == 'b'); // p == 0x1001 which is where the 'b' is...
Wenn Sie Daten haben, in die Sie schreiben können, können Sie so vorgehen:
int x = 2;
int* p_x = &x; // Put the address of the x variable into the pointer p_x
*p_x = 4; // Change the memory at the address in p_x to be 4
assert(x == 4); // Check x is now 4
Sie müssen zur Kompilierzeit gewusst haben, dass Sie eine Variable namens x
und der Code bittet den Compiler, den Speicherort zu bestimmen, um sicherzustellen, dass die Adresse über &x
.
Dereferenzierung und Zugriff auf ein Strukturdatenelement
Wenn Sie in C eine Variable haben, die ein Zeiger auf eine Struktur mit Datenelementen ist, können Sie auf diese Elemente mit der ->
Dereferenzierungsoperator:
typedef struct X { int i_; double d_; } X;
X x;
X* p = &x;
p->d_ = 3.14159; // Dereference and access data member x.d_
(*p).d_ *= -1; // Another equivalent notation for accessing x.d_
Multi-Byte-Datentypen
Um einen Zeiger zu verwenden, benötigt ein Computerprogramm auch einen Einblick in die Art der Daten, auf die gezeigt wird - wenn dieser Datentyp mehr als ein Byte zur Darstellung benötigt, dann zeigt der Zeiger normalerweise auf das niedrigste Byte in den Daten.
Betrachten wir also ein etwas komplexeres Beispiel:
double sizes[] = { 10.3, 13.4, 11.2, 19.4 };
double* p = sizes;
assert(p[0] == 10.3); // Knows to look at all the bytes in the first double value
assert(p[1] == 13.4); // Actually looks at bytes from address p + 1 * sizeof(double)
// (sizeof(double) is almost always eight bytes)
++p; // Advance p by sizeof(double)
assert(*p == 13.4); // The double at memory beginning at address p has value 13.4
*(p + 2) = 29.8; // Change sizes[3] from 19.4 to 29.8
// Note earlier ++p and + 2 here => sizes[3]
Zeiger auf dynamisch zugewiesenen Speicher
Manchmal weiß man nicht, wie viel Speicher man braucht, bis das Programm läuft und sieht, welche Daten ihm zugewiesen werden... dann kann man dynamisch Speicher zuweisen mit malloc
. Es ist gängige Praxis, die Adresse in einem Zeiger zu speichern...
int* p = (int*)malloc(sizeof(int)); // Get some memory somewhere...
*p = 10; // Dereference the pointer to the memory, then write a value in
fn(*p); // Call a function, passing it the value at address p
(*p) += 3; // Change the value, adding 3 to it
free(p); // Release the memory back to the heap allocation library
In C++ erfolgt die Speicherzuweisung normalerweise mit der new
Operator, und die Freigabe mit delete
:
int* p = new int(10); // Memory for one int with initial value 10
delete p;
p = new int[10]; // Memory for ten ints with unspecified initial value
delete[] p;
p = new int[10](); // Memory for ten ints that are value initialised (to 0)
delete[] p;
Siehe auch C++ intelligente Zeiger unten.
Adressenverluste und -lecks
Oft ist ein Zeiger der einzige Hinweis darauf, wo sich Daten oder ein Puffer im Speicher befinden. Wenn eine fortlaufende Verwendung dieser Daten/Puffer erforderlich ist oder die Möglichkeit, die free()
o delete
um ein Auslaufen des Speichers zu vermeiden, muss der Programmierer mit einer Kopie des Zeigers arbeiten...
const char* p = asprintf("name: %s", name); // Common but non-Standard printf-on-heap
// Replace non-printable characters with underscores....
for (const char* q = p; *q; ++q)
if (!isprint(*q))
*q = '_';
printf("%s\n", p); // Only q was modified
free(p);
...oder die Rückgängigmachung von Änderungen sorgfältig zu arrangieren...
const size_t n = ...;
p += n;
...
p -= n; // Restore earlier value...
free(p);
C++ intelligente Zeiger
In C++ ist es die beste Praxis, die intelligenter Zeiger Objekte, um die Zeiger zu speichern und zu verwalten und sie automatisch freizugeben, wenn die Destruktoren der intelligenten Zeiger ausgeführt werden. Seit C++11 bietet die Standardbibliothek zwei, unique_ptr
für den Fall, dass es einen einzigen Eigentümer für ein zugewiesenes Objekt gibt...
{
std::unique_ptr<T> p{new T(42, "meaning")};
call_a_function(p);
// The function above might throw, so delete here is unreliable, but...
} // p's destructor's guaranteed to run "here", calling delete
...und shared_ptr
für Aktienbesitz (unter Verwendung von Referenzzählung )...
{
auto p = std::make_shared<T>(3.14, "pi");
number_storage1.may_add(p); // Might copy p into its container
number_storage2.may_add(p); // Might copy p into its container } // p's destructor will only delete the T if neither may_add copied it
Null-Zeiger
In C, NULL
y 0
- und zusätzlich in C++ nullptr
- kann verwendet werden, um anzuzeigen, dass ein Zeiger derzeit nicht die Speicheradresse einer Variablen enthält und nicht dereferenziert oder in Zeigerarithmetik verwendet werden sollte. Zum Beispiel:
const char* p_filename = NULL; // Or "= 0", or "= nullptr" in C++
int c;
while ((c = getopt(argc, argv, "f:")) != -1)
switch (c) {
case f: p_filename = optarg; break;
}
if (p_filename) // Only NULL converts to false
... // Only get here if -f flag specified
In C und C++ sind die eingebauten numerischen Typen nicht unbedingt standardmäßig auf 0
noch bools
a false
werden Zeiger nicht immer auf NULL
. All diese werden auf 0/false/NULL gesetzt, wenn sie static
Variablen oder (nur in C++) direkte oder indirekte Mitgliedsvariablen von statischen Objekten oder deren Basen, oder sie werden mit Null initialisiert (z. B. new T();
y new T(x, y, z);
eine Null-Initialisierung für die Mitglieder von T, einschließlich Zeigern, durchführen, während new T;
nicht).
Außerdem, wenn Sie 0
, NULL
y nullptr
zu einem Zeiger werden die Bits im Zeiger nicht unbedingt alle zurückgesetzt: Der Zeiger darf auf der Hardware-Ebene keine "0" enthalten oder auf die Adresse 0 in Ihrem virtuellen Adressraum verweisen. Dem Compiler ist es erlaubt, dort etwas anderes zu speichern, wenn er Grund dazu hat, aber was auch immer er tut - wenn Sie kommen und den Zeiger vergleichen mit 0
, NULL
, nullptr
oder einen anderen Zeiger, dem einer dieser Zeiger zugewiesen wurde, muss der Vergleich wie erwartet funktionieren. Unterhalb des Quellcodes auf der Compiler-Ebene ist "NULL" in den Sprachen C und C++ also möglicherweise ein wenig "magisch"...
Mehr über Speicheradressen, und warum Sie das wahrscheinlich nicht wissen müssen
Genauer gesagt, speichern initialisierte Zeiger ein Bitmuster, das entweder NULL
oder eine (oft virtuell ) Speicheradresse.
Im einfachen Fall handelt es sich um einen numerischen Offset in den gesamten virtuellen Adressraum des Prozesses; in komplexeren Fällen kann sich der Zeiger auf einen bestimmten Speicherbereich beziehen, den die CPU auf der Grundlage von CPU-"Segment"-Registern oder einer im Bitmuster kodierten Segment-ID auswählt und/oder je nach den Maschinencodeanweisungen, die die Adresse verwenden, an verschiedenen Stellen sucht.
Zum Beispiel kann ein int*
richtig initialisiert, um auf eine int
Variable könnte - nach dem Casting in eine float*
- Speicher im "GPU"-Speicher zugreifen, der sich von dem Speicher unterscheidet, in dem die int
ist, dann kann sie, wenn sie in einen Funktionszeiger umgewandelt und als solcher verwendet wird, in einen weiteren getrennten Speicher zeigen, der Maschinen-Opcodes für das Programm enthält (mit dem numerischen Wert der Variablen int*
tatsächlich ein zufälliger, ungültiger Zeiger 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 diesen frei derefenzieren (solange die Variable nicht in der Zwischenzeit zerstört/deallokiert wird), und es ist das Problem des Compilers, ob z.B. ein bestimmtes CPU-Segmentregister vorher wiederhergestellt werden muss oder eine bestimmte Maschinencode-Anweisung verwendet wird.
-
Wenn Sie einen Zeiger auf ein Element in einem Array erhalten, können Sie die Zeigerarithmetik verwenden, um sich an eine beliebige Stelle im Array zu bewegen oder sogar eine Adresse ein Stück hinter dem Ende des Arrays zu bilden, die mit anderen Zeigern auf Elemente im Array verglichen werden kann (oder die in ähnlicher Weise durch Zeigerarithmetik auf denselben Wert ein Stück hinter dem Ende bewegt wurden); in C und C++ ist es wiederum Sache des Compilers, sicherzustellen, dass dies "einfach funktioniert".
-
Bestimmte Betriebssystemfunktionen, z. B. die Zuordnung von gemeinsam genutztem Speicher, können Ihnen Zeiger zur Verfügung stellen, die innerhalb des für sie sinnvollen Adressbereichs "einfach funktionieren".
-
Versuche, legale Zeiger über diese Grenzen hinaus zu verschieben, beliebige Zahlen in Zeiger umzuwandeln oder Zeiger zu verwenden, die auf nicht verwandte Typen umgewandelt wurden, haben normalerweise undefiniertes Verhalten Der Code für Betriebssysteme, Gerätetreiber usw. muss sich jedoch möglicherweise auf ein Verhalten stützen, das im C- oder C++-Standard nicht definiert ist, das jedoch durch die spezifische Implementierung oder Hardware gut definiert ist.
Das Dereferenzieren eines Zeigers bedeutet, den Wert zu erhalten, der an der vom Zeiger angegebenen Speicherstelle gespeichert ist. Zu diesem Zweck wird der Operator * verwendet, der auch als Dereferenzierungsoperator bezeichnet wird.
int a = 10;
int* ptr = &a;
printf("%d", *ptr); // With *ptr I'm dereferencing the pointer.
// Which means, I am asking the value pointed at by the pointer.
// ptr is pointing to the location in memory of the variable a.
// In a's location, we have 10. So, dereferencing gives this value.
// Since we have indirect control over a's location, we can modify its content using the pointer. This is an indirect way to access a.
*ptr = 20; // Now a's content is no longer 10, and has been modified to 20.
Ein Zeiger ist ein "Verweis" auf einen Wert ähnlich wie eine Bibliotheksrufnummer ein Verweis auf ein Buch ist. Das "Dereferenzieren" der Rufnummer bedeutet, das Buch physisch zu durchsuchen und abzurufen.
int a=4 ;
int *pA = &a ;
printf( "The REFERENCE/call number for the variable `a` is %p\n", pA ) ;
// The * causes pA to DEREFERENCE... `a` via "callnumber" `pA`.
printf( "%d\n", *pA ) ; // prints 4..
Wenn das Buch nicht da ist, fängt der Bibliothekar an zu schreien, schließt die Bibliothek, und ein paar Leute werden beauftragt, die Ursache dafür zu untersuchen, dass jemand ein Buch sucht, das nicht da ist.
Code und Erklärung von Zeiger-Grundlagen :
Die Dereferenzierungsoperation beginnt bei dem Zeiger und folgt seinem Pfeil über um auf seinen Zeiger zuzugreifen. Das Ziel kann sein den Zustand des Zeigers zu betrachten oder den Zustand des Zeigers zu ändern. Die Dereferenzierungsoperation auf einen Zeiger funktioniert nur, wenn der Zeiger einen Zeiger hat - der Zeiger muss zugewiesen werden und der Zeiger muss so gesetzt werden auf ihn zeigen. Der häufigste Fehler in Zeiger-Code ist das Vergessen, den den Zeiger zu setzen. Der häufigste Laufzeitabsturz aufgrund dieses Fehlers im Code ist eine fehlgeschlagene Dereferenzierung Operation. In Java wird die fehlerhafte Dereferenzierung vom Laufzeitsystem höflich durch das Laufzeitsystem gekennzeichnet. In kompilierten Sprachen wie C, C++, und Pascal, führt die falsche Dereferenzierung manchmal zum Absturz, und manchmal Speicher auf subtile, zufällige Weise beschädigt Weise. Zeigerbugs in kompilierten Sprachen können aus diesem Grund schwer zu aufzuspüren.
void main() {
int* x; // Allocate the pointer x
x = malloc(sizeof(int)); // Allocate an int pointee,
// and set x to point to it
*x = 42; // Dereference x to store 42 in its pointee
}
- See previous answers
- Weitere Antworten anzeigen
0 Stimmen
Das kann Ihnen helfen: stackoverflow.com/questions/2795575/
26 Stimmen
cslibrary.stanford.edu/106
33 Stimmen
int *p;
würde einen Zeiger auf eine Ganzzahl definieren, und*p
würde diesen Zeiger dereferenzieren, d. h. er würde die Daten, auf die p zeigt, tatsächlich abrufen.5 Stimmen
Binky's Pointer Fun ( cslibrary.stanford.edu/104 ) ist ein großartiges Video über Zeiger, das die Dinge klären könnte. @Erik- Toll, dass du den Link zur Stanford CS Library gesetzt hast. Es gibt dort so viele Leckerbissen...
7 Stimmen
Die Antwort von Harry ist hier das Gegenteil von hilfreich.
1 Stimmen
@Peyman
*p
ruft die Daten, auf die p zeigt, nicht ab. Stattdessen bezeichnet er den Speicherort. Dieser Ausdruck kann dann weiter verwendet werden, um entweder neue Daten zu speichern oder Daten abzurufen oder gar nichts.0 Stimmen
Ein passendes Beispiel kam mir gerade in den Sinn, wenn man etwas aus dem Computerspeicher löscht oder man kann sagen, von der Festplatte... eigentlich löscht man nichts, sondern dereferenziert nur den Zeiger, der auf die Adresse der Datei im Speicher zeigt.