Bitte fügen Sie der Erklärung ein Beispiel bei.
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.
Bitte fügen Sie der Erklärung ein Beispiel bei.
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:
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 ++.
Betrachten Sie in C einen Zeiger wie den p
folgenden ...
const char* p = "abc";
... 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:
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 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
.
Um auf die Zeichen zu verweisen, auf die p
verwiesen wird, dereferenzieren wir p
mit 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 verschieben, auf die verwiesen wird, und sie anschließend dereferenzieren:
++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 geschrieben werden kann, können Sie Folgendes tun:
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
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
.
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 :
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_
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:
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]
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 ...
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 dem 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 ++ - Smartpointer unten.
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()
oder delete
Vermeiden eines Speicherverlusts erforderlich ist, muss der Programmierer eine Kopie des Zeigers bearbeiten ...
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 Umkehrung von Änderungen sorgfältig orchestrieren ...
const size_t n = ...;
p += n;
...
p -= n; // Restore earlier value...
free(p);
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 ...
{
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 den Aktienbesitz (unter Verwendung der 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
In C NULL
und 0
- 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:
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 ++, ebenso wie integrierte numerische Typen standardmäßig nicht unbedingt 0
, noch bools
zu false
, Zeiger sind nicht immer auf NULL
. Alle diese sind auf 0 / false / NULL gesetzt , wenn sie static
Variablen oder (nur C ++) direkt oder indirekt Elementvariablen von statischen Objekten oder ihre Basen oder Null Initialisierung durchlaufen (zB new T();
und new T(x, y, z);
führt Null-Initialisierung auf T-Mitgliedern einschließlich Zeiger, während new 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
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 eine int
Variable verweist, nach dem Umwandeln in einen float*
Zugriffsspeicher im "GPU" -Speicher ganz anders sein als der Speicher, in dem sich die int
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 des int*
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.
p[1]
und *(p + 1)
identisch ? Das heißt, erzeugt p[1]
und *(p + 1)
generiert die gleichen Anweisungen?
p
lautet die Basisadresse von nur 2000: Wenn Sie einen anderen Zeiger p
darauf hätten, müssten 2000 in seinen vier oder acht Bytes gespeichert werden. Ich hoffe, das hilft! Prost.
u
ein Array enthält arr
, erkennen sowohl gcc als auch clang, dass der lvalue u.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.
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.
[]
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.
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 sind bereit, die Ursache zu untersuchen, warum eine Person ein Buch findet, das nicht da ist.
In einfachen Worten bedeutet Dereferenzierung den Zugriff auf den Wert von einem bestimmten Speicherort aus, auf den dieser Zeiger zeigt.
Code und Erklärung aus den Zeigergrundlagen :
Die Dereferenzierungsoperation beginnt am Zeiger und folgt seinem Pfeil, um auf seinen Zeiger zuzugreifen. Das Ziel kann sein, den Spitzenstatus zu betrachten oder den Spitzenstatus zu ändern. Die Dereferenzierungsoperation für einen Zeiger funktioniert nur, wenn der Zeiger einen Pointee hat - der Pointee muss zugewiesen und der Zeiger muss so eingestellt sein, dass er darauf zeigt. Der häufigste Fehler im Zeigercode ist das Vergessen, den Pointee einzurichten. Der häufigste Laufzeitabsturz aufgrund dieses Fehlers im Code ist eine fehlgeschlagene Dereferenzierungsoperation. In Java wird die falsche Dereferenzierung vom Laufzeitsystem höflich gekennzeichnet. In kompilierten Sprachen wie C, C ++ und Pascal stürzt die falsche Dereferenzierung manchmal ab und manchmal beschädigt sie den Speicher auf subtile, zufällige Weise.
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
}
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
Es arbeitet mit einer Zeigervariablen und gibt einen l-Wert zurück, der dem Wert an der Zeigeradresse entspricht. Dies wird als "Dereferenzieren" des Zeigers bezeichnet.
Das heißt, wir können den Zeiger dereferenzieren, ohne jemals auf den Wert zuzugreifen, auf den er zeigt. Zum Beispiel:
char *p = NULL;
*p;
Wir haben den NULL-Zeiger dereferenziert, ohne auf seinen Wert zuzugreifen. Oder wir könnten tun:
p1 = &(*p);
sz = sizeof(*p);
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.
*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.