variable - pointer to int c++




Was bedeutet "Dereferenzierung" eines Zeigers? (4)

Überprüfung der grundlegenden Terminologie

Es ist normalerweise gut genug, wenn Sie nicht die Assembly programmieren, sich einen Zeiger vorzustellen, der eine numerische Speicheradresse enthält, wobei sich 1 auf das zweite Byte im Prozessspeicher bezieht, 2 auf die dritte, 3 auf die vierte und so weiter ....

  • Was ist mit 0 und dem ersten Byte passiert? Nun, dazu kommen wir später - siehe unten die Nullzeiger .
  • Für eine genauere Definition dessen, was Zeiger speichern und wie Speicher und Adressen sich beziehen, siehe "Mehr über Speicheradressen und warum Sie wahrscheinlich nicht wissen müssen" .

Wenn Sie auf die Daten / Werte im Speicher zugreifen möchten, auf die der Zeiger zeigt - den Inhalt der Adresse mit diesem numerischen Index -, dann dereferenzieren Sie den Zeiger.

Verschiedene Computersprachen haben verschiedene Notationen, um dem Compiler oder Interpreter mitzuteilen, dass Sie sich jetzt für den angegebenen Wert interessieren - ich konzentriere mich auf C und C ++.

Ein Zeigerszenario

Betrachten Sie in C, gegeben einen Zeiger wie p unten ...

const char* p = "abc";

... vier Bytes mit den numerischen Werten, die zum Codieren der Buchstaben "a", "b", "c" und einem 0-Byte verwendet werden, um das Ende der Textdaten zu bezeichnen, werden irgendwo im Speicher und der numerischen Adresse davon gespeichert Daten werden in p gespeichert.

Wenn das Zeichenfolgenliteral beispielsweise die Adresse 0x1000 und p einen 32-Bit-Zeiger bei 0x2000 hat, lautet der Speicherinhalt:

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 / -bezeichner für die Adresse 0x1000 gibt, aber wir können indirekt auf das Zeichenfolgenliteral verweisen, indem Sie einen Zeiger verwenden, der seine Adresse speichert: p .

Dereferenzieren des Zeigers

Um auf die Zeichen zu verweisen, auf die p zeigt, demeferenzieren 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 Zeiger auch durch die angegebenen Daten verschieben, indem Sie sie bei der Suche dereferenzieren:

++p;  // Increment p so it's now 0x1001
assert(*p == 'b');  // p == 0x1001 which is where the 'b' is...

Wenn Sie Daten haben, auf 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 Variable namens x benötigen, und der Code fragt den Compiler, wo er gespeichert werden soll, und stellt sicher, dass die Adresse über &x verfügbar ist.

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 Operator -> Dereferenzierung 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_

Multi-Byte-Datentypen

Um einen Zeiger zu verwenden, benötigt ein Computerprogramm auch einen Einblick in die Art von Daten, auf die gezeigt wird - wenn dieser Datentyp mehr als ein Byte zum Darstellen benötigt, dann zeigt der Zeiger normalerweise auf das Byte mit der niedrigsten Nummer in den Daten.

Betrachten wir 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)
assert(++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 wissen Sie nicht, wie viel Speicher Sie benötigen, bis Ihr Programm ausgeführt wird und welche Daten darauf geworfen werden. Dann können Sie Speicher dynamisch mithilfe von malloc zuweisen. Es ist üblich, die Adresse in einem Zeiger zu speichern ...

int* p = 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.

Adressen verlieren und verlieren

Häufig kann ein Zeiger der einzige Hinweis darauf sein, wo einige Daten oder Puffer im Speicher vorhanden sind. Wenn eine fortlaufende Verwendung dieser Daten / Puffer erforderlich ist oder die Möglichkeit besteht, free() oder delete aufzurufen, um ein Lecken des Speichers zu vermeiden, muss der Programmierer eine Kopie des Zeigers ausführen ...

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 sorgfältig die Umkehrung von Änderungen koordinieren ...

const size_t n = ...;
p += n;
...
p -= n;  // Restore earlier value...

C ++ - Smartpointer

In C ++ empfiehlt es sich, Smart Pointer- Objekte zu verwenden, um die Zeiger zu speichern und zu verwalten, wobei sie automatisch freigegeben werden, wenn die Destruktoren der Smartpointer ausgeführt werden. Seit C ++ 11 bietet die Standardbibliothek zwei, unique_ptr wenn es einen einzigen Besitzer für ein zugeordnetes 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 (mittels Referenzzählung ) ...

{
    std::shared_ptr<T> p(new T(3.14, "pi"));
    number_storage.may_add(p); // Might copy p into its container
} // p's destructor will only delete the T if number_storage didn't copy

Null Zeiger

In C, NULL und 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. Beispielsweise:

const char* p_filename = NULL; // Or "= 0", or "= nullptr" in C++
char c;
while ((c = getopt(argc, argv, "f:")) != EOF)
    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 ++ müssen Zeiger genauso wie eingebaute numerische Typen nicht notwendigerweise auf 0 oder bools auf false , Zeiger werden nicht immer auf NULL . Alle diese werden auf 0 / false / NULL gesetzt, wenn sie static Variablen oder (nur C ++) direkte oder indirekte Membervariablen von statischen Objekten oder deren Basen sind oder einer Nullinitialisierung unterzogen werden (z. B. new T(); und new T(x, y, z); new T; führe eine Null-Initialisierung an T-Elementen einschließlich Zeigern durch, während das new T; nicht gilt.

Wenn Sie einem Zeiger außerdem 0 , NULL und nullptr zuweisen, werden die Bits im Zeiger nicht notwendigerweise alle zurückgesetzt: Der Zeiger enthält möglicherweise keine "0" auf Hardwareebene oder verweist auf Adresse 0 in Ihrem virtuellen Adressraum. Der Compiler kann etwas anderes dort speichern, wenn es einen Grund dafür gibt, aber was immer es auch tut - wenn Sie den Zeiger mit 0 , NULL , nullptr oder einem anderen Zeiger vergleichen, der einer davon zugewiesen wurde, muss der Vergleich wie erwartet funktionieren . Unterhalb des Quellcodes auf der Compiler-Ebene ist "NULL" also möglicherweise ein bisschen "magisch" in den C- und C ++ - Sprachen ...

Mehr über Speicheradressen und warum Sie wahrscheinlich nicht wissen müssen

Streng genommen speichern initialisierte Zeiger ein Bitmuster, das entweder NULL oder eine (häufig virtual ) Speicheradresse identifiziert.

Im einfachen Fall ist dies ein numerischer Offset in den gesamten virtuellen Adressraum des Prozesses; 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 in dem Bitmuster codiert sind, und / oder an verschiedenen Stellen in Abhängigkeit von Maschinencode-Anweisungen unter Verwendung der Adresse.

Beispielsweise kann ein int* richtig initialisiert wurde, um auf eine int Variable zu zeigen, nach dem Umwandeln in einen float* auf einen Wert im "GPU" -Memory zugreifen, der sich von der int Variable unterscheidet Halten Sie die Maschine Opcodes für die Funktion.

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 ihn frei dereferenzieren (solange die Variable nicht zerstört / dealloziert ist) und es ist das Problem des Compilers, ob zB ein bestimmtes CPU-Register vorher oder ein distinct wiederhergestellt werden muss Maschinencode-Anweisung verwendet

  • 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 nach dem Ende des Arrays zu bilden, die mit anderen Zeigern zu Elementen vergleichbar ist in der Anordnung (oder die ähnlich durch Zeigerarithmetik auf den gleichen Wert über die Endlage hinaus bewegt worden sind); in C und C ++ ist es Sache des Compilers, dafür zu sorgen, dass das "einfach funktioniert"

  • Spezifische OS-Funktionen für zB Shared Memory Mapping können Ihnen Zeiger geben, und sie "funktionieren" nur innerhalb des Bereichs von Adressen, der für sie sinnvoll ist

  • Versuche, legale Zeiger über diese Grenzen hinaus zu verschieben oder Zeiger auf beliebige Zeiger zu werfen oder Zeiger auf nicht verwandte Typen zu verwenden, haben normalerweise undefiniertes Verhalten , sollten also in höheren Bibliotheken und Anwendungen vermieden werden, aber Code für Betriebssysteme, Gerätetreiber usw Möglicherweise müssen Sie sich auf ein Verhalten verlassen, das von C oder C ++ undefiniert ist, das von der jeweiligen Hardware jedoch gut definiert ist.

Bitte fügen Sie ein Beispiel mit der Erklärung bei.


Code und Erklärung von cslibrary.stanford.edu/106 :

Die Dereferenzierungsoperation beginnt am Zeiger und folgt ihrem Pfeil, um auf ihren Zeiger zuzugreifen. Das Ziel kann sein, den Pointe-Status zu betrachten oder den Pointe-Status zu ändern. Die Dereferenzierungsoperation für einen Zeiger funktioniert nur, wenn der Zeiger einen Zeiger hat - der Zeiger muss zugeordnet sein und der Zeiger muss so eingestellt sein, dass er darauf zeigt. Der häufigste Fehler im Zeigercode besteht darin, zu vergessen, den Zeiger einzurichten. Der häufigste Laufzeitabsturz aufgrund dieses Fehlers im Code ist eine fehlgeschlagene Dereferenzierungsoperation. In Java wird die fehlerhafte 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 irgendeine subtile, zufällige Weise. Zeigerfehler in kompilierten Sprachen können aus diesem Grund schwer zu finden sein.

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   
}

Ein Zeiger ist eine "Referenz" zu einem Wert. Ähnlich wie eine Bibliotheksrufnummer ist eine Referenz auf ein Buch. Die Dereferenzierung der Rufnummer erfolgt physisch und ruft das Buch ab.

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 suchen nach der Ursache für eine Person, die ein Buch findet, das nicht da ist.


Ich denke, alle vorherigen Antworten sind falsch, da sie angeben, dass Dereferenzierung den Zugriff auf den tatsächlichen Wert bedeutet. Wikipedia gibt stattdessen die richtige Definition: 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 "Dereferenzierung" des Zeigers bezeichnet.

Das heißt, wir können den Zeiger dereferenzieren, ohne jemals auf den Wert zuzugreifen, auf den er zeigt. Beispielsweise:

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 nie auf den Wert zugreifen. Ein solcher Code stürzt NICHT ab: Der Absturz geschieht, wenn Sie tatsächlich auf die Daten mit einem ungültigen Zeiger zugreifen . Laut dem Standard ist die Dereferenzierung eines ungültigen Zeigers leider (mit einigen Ausnahmen) ein undefiniertes Verhalten, auch wenn Sie nicht versuchen, die tatsächlichen Daten zu berühren.

Kurz gesagt: Dereferenzierung des Zeigers bedeutet, dass der Dereferenzierungsoperator darauf angewendet wird. Dieser Operator gibt nur einen l-Wert für Ihre zukünftige Verwendung zurück.





dereference