Cosa significa "dereferenziare" un puntatore?


541

Si prega di includere un esempio con la spiegazione.




24
int *p;definirebbe un puntatore a un numero intero e *pfarebbe la dereferenza a quel puntatore, nel senso che recupererebbe effettivamente i dati a cui p punta.
Peyman,

4
Binky's Pointer Fun ( cslibrary.stanford.edu/104 ) è un GRANDE video sui puntatori che potrebbe chiarire le cose. @ Erik- Fai rock per aver messo il link alla Stanford CS Library. Ci sono così tante prelibatezze lì ...
templatetypedef,

6
La risposta di Harry è l'opposto di utile qui.
Jim Balter,

Risposte:


731

Revisione della terminologia di base

Di solito è abbastanza buono - a meno che non si stia programmando un assembly - prevedere un puntatore contenente un indirizzo di memoria numerico, con 1 che si riferisce al secondo byte nella memoria del processo, 2 il terzo, 3 il quarto e così via ....

  • Cosa è successo a 0 e al primo byte? Bene, ci arriveremo più avanti - vedi sotto i puntatori null .
  • Per una definizione più accurata di cosa memorizzano i puntatori e di come si collegano memoria e indirizzi, vedere "Altre informazioni sugli indirizzi di memoria e perché probabilmente non è necessario saperlo" alla fine di questa risposta.

Quando si desidera accedere ai dati / valore nella memoria che i punti di puntatore - il contenuto del l'indirizzo con che indice numerico - allora si dereference il puntatore.

Diversi linguaggi per computer hanno notazioni diverse per dire al compilatore o all'interprete che ora sei interessato al valore (attuale) dell'oggetto puntato - Mi concentro di seguito su C e C ++.

Uno scenario puntatore

Considera in C, dato un puntatore come psotto ...

const char* p = "abc";

... quattro byte con i valori numerici usati per codificare le lettere 'a', 'b', 'c' e uno 0 byte per indicare la fine dei dati testuali, sono memorizzati da qualche parte nella memoria e l'indirizzo numerico di quello i dati sono archiviati in p. In questo modo C codifica il testo in memoria è noto come ASCIIZ .

Ad esempio, se la stringa letterale si trovasse all'indirizzo 0x1000 e pun puntatore a 32 bit a 0x2000, il contenuto della memoria sarebbe:

Memory Address (hex)    Variable name    Contents
1000                                     'a' == 97 (ASCII)
1001                                     'b' == 98
1002                                     'c' == 99
1003                                     0
...
2000-2003               p                1000 hex

Si noti che non v'è alcun nome / identificatore variabile per l'indirizzo 0x1000, ma possiamo indirettamente riferimento alla stringa letterale usando un puntatore memorizzare il suo indirizzo: p.

Dereferenziare il puntatore

Per fare riferimento ai caratteri pa cui puntiamo, facciamo una deduzione pusando una di queste notazioni (di nuovo, per 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]

Puoi anche spostare i puntatori attraverso i dati puntati, dereferenziandoli mentre procedi:

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

Se disponi di alcuni dati su cui puoi scrivere, puoi fare cose del genere:

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

Sopra, devi sapere al momento della compilazione che avresti bisogno di una variabile chiamata xe il codice chiede al compilatore di organizzare dove dovrebbe essere archiviato, assicurando che l'indirizzo sia disponibile via &x.

Dereferenziazione e accesso a un membro di dati della struttura

In C, se hai una variabile che è un puntatore a una struttura con membri di dati, puoi accedere a quei membri usando l' ->operatore di dereferencing:

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_

Tipi di dati multibyte

Per utilizzare un puntatore, un programma per computer ha anche bisogno di alcune informazioni sul tipo di dati a cui viene puntato: se quel tipo di dati richiede più di un byte per rappresentare, il puntatore normalmente punta al byte con il numero più basso nei dati.

Quindi, guardando un esempio leggermente più complesso:

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]

Puntatori alla memoria allocata dinamicamente

A volte non sai quanta memoria ti servirà fino a quando il tuo programma non sarà in esecuzione e vedrai quali dati vengono lanciati su di essa ... quindi puoi allocare dinamicamente la memoria usando malloc. È pratica comune memorizzare l'indirizzo in un puntatore ...

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 ++, l'allocazione della memoria viene normalmente eseguita con l' newoperatore e la deallocazione con 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;

Vedi anche i puntatori intelligenti C ++ di seguito.

Perdere e perdere indirizzi

Spesso un puntatore può essere l'unica indicazione della posizione di alcuni dati o buffer nella memoria. Se è necessario un uso continuo di tali dati / buffer o la possibilità di chiamare free()o deleteevitare perdite di memoria, il programmatore deve operare su una copia del puntatore ...

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);

... o orchestrare attentamente l'inversione di qualsiasi modifica ...

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

Puntatori intelligenti C ++

In C ++, è consigliabile utilizzare oggetti puntatore intelligente per archiviare e gestire i puntatori, deallocandoli automaticamente quando vengono eseguiti i distruttori dei puntatori intelligenti. Da C ++ 11 la Libreria standard ne fornisce due, unique_ptrper quando esiste un unico proprietario per un oggetto allocato ...

{
    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

... e shared_ptrper la proprietà azionaria (utilizzando il conteggio dei riferimenti ) ...

{
    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

Puntatori null

In C NULLe 0- e in aggiunta in C ++ nullptr- possono essere usati per indicare che un puntatore non contiene attualmente l'indirizzo di memoria di una variabile e non dovrebbe essere dereferenziato o usato nell'aritmetica del puntatore. Per esempio:

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 e C ++, così come i tipi numerici incorporati non necessariamente predefinito a 0, nè boolsa false, i puntatori non sono sempre impostati NULL. Tutti questi sono impostati a 0 / false / NULL quando sono staticvariabili o (C ++ solo) variabili membro dirette o indirette di oggetti statici o le loro basi, o sottoposti a zero inizializzazione (ad esempio, new T();e new T(x, y, z);svolgono zero inizializzazione sui membri del T compresi i puntatori, mentre new T;non).

Inoltre, quando si assegnano 0, NULLe nullptrad un puntatore, i bit nel puntatore non sono necessariamente tutti ripristinati: il puntatore potrebbe non contenere "0" a livello hardware o fare riferimento all'indirizzo 0 nel proprio spazio di indirizzi virtuale. Il compilatore è permesso di negozio qualcos'altro lì, se ha motivo di, ma qualunque cosa faccia - se si arriva lungo e confrontare il puntatore 0, NULL, nullptro un altro puntatore che è stato assegnato una di queste, il lavoro di confronto must come previsto. Quindi, sotto il codice sorgente a livello di compilatore, "NULL" è potenzialmente un po '"magico" nei linguaggi C e C ++ ...

Ulteriori informazioni sugli indirizzi di memoria e sul perché probabilmente non è necessario saperlo

Più rigorosamente, i puntatori inizializzati memorizzano un modello di bit che identifica uno NULLo un indirizzo di memoria (spesso virtuale ).

Il semplice caso è dove questo è un offset numerico nell'intero spazio degli indirizzi virtuali del processo; in casi più complessi il puntatore può essere relativo ad un'area di memoria specifica, che la CPU può selezionare in base ai registri "segmenti" della CPU o in qualche modo di id di segmento codificato nel bit-pattern e / o guardare in luoghi diversi a seconda del istruzioni sul codice macchina utilizzando l'indirizzo.

Ad esempio, un int*inizializzato correttamente per puntare a una intvariabile potrebbe - dopo il cast in una float*- accedere alla memoria nella memoria "GPU" abbastanza distinta dalla memoria in cui si trova la intvariabile, quindi una volta lanciata e utilizzata come puntatore a funzione potrebbe puntare ulteriormente codici opzionali distinti di memoria della macchina per il programma (con il valore numerico di int*un puntatore casuale e non valido all'interno di queste altre aree di memoria).

I linguaggi di programmazione 3GL come C e C ++ tendono a nascondere questa complessità, in modo tale che:

  • Se il compilatore ti dà un puntatore a una variabile o a una funzione, puoi dereferenziarla liberamente (purché la variabile non sia distrutta / deallocata nel frattempo) ed è il problema del compilatore se, ad esempio, è necessario ripristinare in anticipo un determinato registro di segmenti CPU o un istruzioni distinte di codice macchina utilizzate

  • Se si ottiene un puntatore a un elemento in un array, è possibile utilizzare l'aritmetica del puntatore per spostarsi in qualsiasi altro punto dell'array o persino per formare un indirizzo oltre la fine dell'array che è legale per confrontare con altri puntatori a elementi nell'array (o che sono stati spostati in modo simile dall'aritmetica del puntatore sullo stesso valore uno-oltre-fine); sempre in C e C ++, spetta al compilatore assicurarsi che "funzioni"

  • Funzioni specifiche del sistema operativo, ad esempio la mappatura della memoria condivisa, possono darti suggerimenti e "funzioneranno" all'interno della gamma di indirizzi che ha senso per loro

  • I tentativi di spostare i puntatori legali oltre questi limiti, o di lanciare numeri arbitrari su puntatori o di utilizzare puntatori lanciati su tipi non correlati, in genere hanno un comportamento indefinito , quindi dovrebbero essere evitati in librerie e applicazioni di livello superiore, ma codice per sistemi operativi, driver di dispositivo, ecc. potrebbe essere necessario fare affidamento su comportamenti lasciati non definiti dallo standard C o C ++, che è comunque ben definito dalla loro specifica implementazione o hardware.


è p[1] e *(p + 1) identico ? Cioè, genera p[1] e *(p + 1)genera le stesse istruzioni?
Pacerier,

2
@Pacerier: dal 6.5.2.1/2 nella bozza di norma C N1570 (la prima l'ho trovata online) "La definizione dell'operatore di sottoscrizione [] è che E1 [E2] è identico a (* ((E1) + (E2)) )." - Non riesco a immaginare alcun motivo per cui un compilatore non li converta immediatamente in rappresentazioni identiche in una fase iniziale della compilazione, applicando le stesse ottimizzazioni dopo ciò, ma non vedo come chiunque possa sicuramente provare che il codice sarebbe identico senza esaminare ogni compilatore mai scritto.
Tony Delroy,

3
@Honey: il valore 1000 hex è troppo grande per essere codificato in un singolo byte (8 bit) di memoria: è possibile memorizzare solo numeri senza segno da 0 a 255 in un byte. Quindi, non è possibile memorizzare 1000 esadecimali a "solo" l'indirizzo 2000. Invece, un sistema a 32 bit userebbe 32 bit - che sono quattro byte - con indirizzi dal 2000 al 2003. Un sistema a 64 bit userebbe 64 bit - 8 byte - dal 2000 al 2007. In entrambi i casi, l'indirizzo di base pè solo 2000: se avessi un altro puntatore ad pesso dovrebbe archiviare 2000 nei suoi quattro o otto byte. Spero che aiuti! Saluti.
Tony Delroy,

1
@TonyDelroy: se un'unione ucontiene un array arr, sia gcc che clang riconosceranno che il lvalue u.arr[i]potrebbe accedere allo stesso archivio degli altri membri dell'unione, ma non riconoscerà che lvalue *(u.arr+i)potrebbe farlo. Non sono sicuro se gli autori di quei compilatori pensino che quest'ultimo invochi UB o che il primo invochi UB ma dovrebbero comunque elaborarlo utilmente, ma vedono chiaramente le due espressioni come diverse.
supercat

3
Raramente ho visto i puntatori e il loro uso in C / C ++ in modo così conciso e semplice.
kayleeFrye_onDeck

102

Dereferenziare un puntatore significa ottenere il valore archiviato nella posizione di memoria puntato dal puntatore. L'operatore * viene utilizzato per eseguire questa operazione e viene chiamato operatore di dereferenziazione.

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.

15
Un puntatore non punta a un valore , punta a un oggetto .
Keith Thompson,

52
@KeithThompson Un puntatore non punta a un oggetto, punta a un indirizzo di memoria, dove si trova un oggetto (forse una primitiva).
mg30rg

4
@ mg30rg: non sono sicuro di quale distinzione stai facendo. Un valore di puntatore è un indirizzo. Un oggetto, per definizione, è una "regione di archiviazione dei dati nell'ambiente di esecuzione, il cui contenuto può rappresentare valori". E cosa intendi con "primitivo"? Lo standard C non usa questo termine.
Keith Thompson,

6
@KeithThompson Stavo a malapena sottolineando che in realtà non hai aggiunto valore alla risposta, stavi solo pignolando sulla terminologia (e hai fatto anche quello sbagliato). Il valore del puntatore è sicuramente un indirizzo, è così che "punta" a un indirizzo di memoria. La parola "oggetto" nel nostro mondo guidato da OOP può essere fuorviante, perché può essere interpretata come "istanza di classe" (sì, non sapevo che la domanda fosse etichettata [C] e non [C ++]), e ho usato la parola "primitivo" come al contrario di "copmlex" (struttura di dati come una struttura o classe).
mg30rg

3
Consentitemi di aggiungere a questa risposta che l'operatore di sottoscrizione di array []dereferenzia anche un puntatore ( a[b]è definito come medio *(a + b)).
cmaster - ripristina monica il

20

Un puntatore è un "riferimento" a un valore. Proprio come un numero di chiamata della biblioteca è un riferimento a un libro. "Dereferenziazione" il numero di chiamata sta fisicamente attraversando e recuperando quel libro.

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.. 

Se il libro non è lì, il bibliotecario inizia a gridare, chiude la biblioteca e un paio di persone sono disposte a indagare sulla causa di una persona che troverà un libro che non c'è.


17

In parole semplici, dereferenziare significa accedere al valore da una determinata posizione di memoria contro cui punta quel puntatore.


7

Codice e spiegazione dalle basi del puntatore :

L'operazione di dereference inizia dal puntatore e segue la sua freccia sopra per accedere alla sua punta. L'obiettivo potrebbe essere quello di guardare lo stato della punta o cambiare lo stato della punta. L'operazione di dereference su un puntatore funziona solo se il puntatore ha una punta: la punta deve essere allocata e il puntatore deve essere impostato per puntare su di esso. L'errore più comune nel codice del puntatore è dimenticare di impostare la punta. L'arresto anomalo del runtime più comune a causa di tale errore nel codice è un'operazione di dereference non riuscita. In Java la dereferenza errata verrà segnalata educatamente dal sistema di runtime. In linguaggi compilati come C, C ++ e Pascal, la dereferenza errata a volte si arresta in modo anomalo e altre volte corrompe la memoria in modo sottile e casuale.

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   
}

In realtà devi allocare memoria per dove dovrebbe puntare x. Il tuo esempio ha un comportamento indefinito.
Peyman,

3

Penso che tutte le risposte precedenti siano sbagliate, in quanto affermano che la dereferenziazione significa accedere al valore reale. Wikipedia fornisce invece la definizione corretta: https://en.wikipedia.org/wiki/Dereference_operator

Funziona su una variabile puntatore e restituisce un valore l equivalente al valore all'indirizzo del puntatore. Questo si chiama "dereferencing" il puntatore.

Detto questo, possiamo dereferenziare il puntatore senza mai accedere al valore a cui punta. Per esempio:

char *p = NULL;
*p;

Abbiamo referenziato il puntatore NULL senza accedere al suo valore. Oppure potremmo fare:

p1 = &(*p);
sz = sizeof(*p);

Ancora una volta, dereferenziando, ma non accedendo mai al valore. Tale codice NON si arresta in modo anomalo: l'arresto anomalo si verifica quando si accede effettivamente ai dati da un puntatore non valido. Tuttavia, sfortunatamente, secondo lo standard, la dereferenziazione di un puntatore non valido è un comportamento indefinito (con alcune eccezioni), anche se non si tenta di toccare i dati effettivi.

Quindi in breve: dereferenziare il puntatore significa applicare ad esso l'operatore dereference. Quell'operatore restituisce un valore l per il tuo uso futuro.


bene, hai fatto riferimento a un puntatore NULL, che avrebbe portato a un errore di segmentazione.
Arjun Gaur,

inoltre hai cercato "operatore di dereferenziazione" e non "dereferenziare un puntatore", il che significa effettivamente ottenere il valore / accedere a un valore in una posizione di memoria a cui punta un puntatore.
Arjun Gaur,

Hai provato? L'ho fatto. Il seguente non si arresta in modo anomalo: `#include <stdlib.h> int main () {char * p = NULL; * p; ritorna 0; } `
stsp,

1
@stsp Lo fa perché il codice non si arresta in modo anomalo ora non significa che non lo farà in futuro o su un altro sistema.

1
*p;provoca un comportamento indefinito. Anche se hai ragione che dereferenziazione non accedere al valore di per sé , il codice *p; fa accedere al valore.
MM
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.