Post-incremento su un puntatore dereferenziato?

Cercando di capire il comportamento dei puntatori in C, sono rimasto un po ‘sorpreso dal seguente (esempio di codice di seguito):

#include  void add_one_v1(int *our_var_ptr) { *our_var_ptr = *our_var_ptr +1; } void add_one_v2(int *our_var_ptr) { *our_var_ptr++; } int main() { int testvar; testvar = 63; add_one_v1(&(testvar)); /* Try first version of the function */ printf("%d\n", testvar); /* Prints out 64 */ printf("@ %p\n\n", &(testvar)); testvar = 63; add_one_v2(&(testvar)); /* Try first version of the function */ printf("%d\n", testvar); /* Prints 63 ? */ printf("@ %p\n", &(testvar)); /* Address remains identical */ } 

Produzione:

 64 @ 0xbf84c6b0 63 @ 0xbf84c6b0 

Che cosa fa esattamente l’ *our_var_ptr++ nella seconda funzione ( add_one_v2 ) poiché chiaramente non è uguale a *our_var_ptr = *our_var_ptr +1 ?

A causa delle regole di precedenza degli operatori e del fatto che ++ è un operatore postfisso, add_one_v2() esegue il dereferenziazione del puntatore, ma il ++ viene effettivamente applicato al puntatore stesso . Tuttavia, ricorda che C usa sempre il valore pass-by: add_one_v2() sta incrementando la sua copia locale del puntatore, che non avrà alcun effetto sul valore memorizzato a quell’indirizzo.

Come test, sostituire add_one_v2() con questi bit di codice e vedere come viene influenzato l’output:

 void add_one_v2(int *our_var_ptr) { (*our_var_ptr)++; // Now stores 64 } void add_one_v2(int *our_var_ptr) { *(our_var_ptr++); // Increments the pointer, but this is a local // copy of the pointer, so it doesn't do anything. } 

Questo è uno di quei trucchi che rendono C e C ++ così divertenti. Se vuoi piegare il cervello, immagina questo:

 while (*dst++ = *src++) ; 

È una copia di corda. I puntatori continuano ad essere incrementati fino a quando non viene copiato un personaggio con un valore pari a zero. Una volta che sai perché questo trucco funziona, non dimenticherai mai come ++ funziona di nuovo sui puntatori.

PS È sempre ansible ignorare l’ordine dell’operatore con parentesi. Quanto segue incrementerà il valore puntato a, piuttosto che il puntatore stesso:

 (*our_var_ptr)++; 

OK,

 *our_var_ptr++; 

funziona così:

  1. Il dereferenziamento avviene per primo, dandovi la posizione di memoria indicata da our_var_ptr (che contiene 63).
  2. Quindi viene valutata l’espressione, il risultato di 63 è ancora 63.
  3. Il risultato è buttato via (non stai facendo nulla con esso).
  4. our_var_ptr viene quindi incrementato DOPO la valutazione. Sta cambiando dove punta il puntatore, non a cosa sta puntando.

È effettivamente lo stesso di fare questo:

 *our_var_ptr; our_var_ptr = our_var_ptr + 1; 

Ha senso? La risposta di Mark Ransom ne è un buon esempio, tranne che in realtà usa il risultato.

Come hanno sottolineato gli altri, la precedenza degli operatori fa sì che l’espressione nella funzione v2 sia vista come *(our_var_ptr++) .

Tuttavia, poiché questo è un operatore post-incremento, non è del tutto vero affermare che incrementa il puntatore e quindi lo dereferisce. Se questo fosse vero, non penso che otterresti 63 come output, dal momento che restituirebbe il valore nella prossima posizione di memoria. In realtà, credo che la sequenza logica delle operazioni sia:

  1. Salva il valore corrente del puntatore
  2. Aumenta il puntatore
  3. Dereferenziare il valore del puntatore salvato nel passaggio 1

Come spiegato in precedenza, non si sta visualizzando la modifica del valore del puntatore perché viene passato per valore alla funzione.

C’è molta confusione qui, quindi ecco un programma di test modificato per rendere ciò che accade chiaro (o almeno chiaro):

 #include  void add_one_v1(int *p){ printf("v1: pre: p = %p\n",p); printf("v1: pre: *p = %d\n",*p); *p = *p + 1; printf("v1: post: p = %p\n",p); printf("v1: post: *p = %d\n",*p); } void add_one_v2(int *p) { printf("v2: pre: p = %p\n",p); printf("v2: pre: *p = %d\n",*p); int q = *p++; printf("v2: post: p = %p\n",p); printf("v2: post: *p = %d\n",*p); printf("v2: post: q = %d\n",q); } int main() { int ary[2] = {63, -63}; int *ptr = ary; add_one_v1(ptr); printf("@ %p\n", ptr); printf("%d\n", *(ptr)); printf("%d\n\n", *(ptr+1)); add_one_v2(ptr); printf("@ %p\n", ptr); printf("%d\n", *ptr); printf("%d\n", *(ptr+1)); } 

con l’output risultante:

 v1: pre: p = 0xbfffecb4 v1: pre: *p = 63 v1: post: p = 0xbfffecb4 v1: post: *p = 64 @ 0xbfffecb4 64 -63 v2: pre: p = 0xbfffecb4 v2: pre: *p = 64 v2: post: p = 0xbfffecb8 v2: post: *p = -63 v2: post: q = 64 @ 0xbfffecb4 64 -63 

Quattro cose da notare:

  1. le modifiche alla copia locale del puntatore non si riflettono nel puntatore chiamante.
  2. le modifiche alla destinazione del puntatore locale influiscono sulla destinazione del puntatore chiamante (almeno finché il puntatore di destinazione non viene aggiornato)
  3. il valore puntato in add_one_v2 non viene incrementato e nessuno dei due è il seguente valore, ma il puntatore lo è
  4. l’incremento del puntatore in add_one_v2 avviene dopo la dereferenziazione

Perché?

  • Perché ++ lega più strettamente di * (come dereferenziazione o moltiplicazione) quindi l’incremento in add_one_v2 applica al puntatore e non a ciò a cui punta.
  • gli incrementi di post avvengono dopo la valutazione del termine, quindi il dereferenziatore ottiene il primo valore nell’array (elemento 0).

our_var_ptr è un puntatore a qualche memoria. cioè è la cella di memoria in cui sono memorizzati i dati. (n questo caso 4 byte nel formato binario di un int).

* our_var_ptr è il puntatore dereferenziato – va alla posizione in cui punta il puntatore.

++ incrementa un valore.

così. *our_var_ptr = *our_var_ptr+1 dereferenzia il puntatore e ne aggiunge uno al valore in quella posizione.

Ora aggiungi la precedenza dell’operatore – leggi come (*our_var_ptr) = (*our_var_ptr)+1 e vedi che il dereferenziamento avviene per primo, quindi prendi il valore e incrementalo.

Nel tuo altro esempio, l’operatore ++ ha una precedenza inferiore rispetto a *, quindi accetta il puntatore che hai passato, ne aggiunge uno (quindi punta alla spazzatura ora) e poi ritorna. (ricorda che i valori vengono sempre passati per valore in C, quindi quando la funzione restituisce il puntatore originale di testvar rimane lo stesso, hai solo cambiato il puntatore all’interno della funzione).

Il mio consiglio, quando si utilizza il dereferenziamento (o qualsiasi altra cosa), usa parentesi per rendere esplicita la tua decisione. Non cercare di ricordare le regole di precedenza, perché un giorno finirai per usare un’altra lingua che le avrà leggermente diverse e ti confonderai. O vecchio e finiscono per dimenticare che ha una precedenza più alta (come faccio con * e ->).

L’operatore ‘++’ ha precedenza più alta rispetto all’operatore ‘*’, il che significa che l’indirizzo del puntatore verrà incrementato prima di essere dereferenziato.

L’operatore ‘+’, tuttavia, ha precedenza inferiore a ‘*’.

Proverò a rispondere da un angolo diverso … Passo 1 Diamo un’occhiata agli operatori e agli operandi: in questo caso è l’operando, e hai due operatori, in questo caso * per il dereferenziamento e ++ per incremento. Passaggio 2 che ha la precedenza più alta ++ ha una precedenza superiore a * Passaggio 3 Dov’è ++, è a destra che significa Incremento POST In questo caso, il compilatore prende una ‘nota mentale’ per eseguire l’incremento DOPO che è fatto con tutti gli altri operatori … nota se era * ++ p allora lo farà PRIMA quindi in questo caso è equivalente a prendere due dei registri del processore, uno terrà il valore del dereferenziato * p e l’altro conterrà il valore del p ++ incrementato, la ragione in questo caso ce ne sono due, è l’attività POST … Qui è dove in questo caso è difficile, e sembra una contraddizione. Ci si aspetterebbe che il ++ abbia la precedenza su *, cosa che fa, solo che il POST significa che sarà applicato solo dopo TUTTI gli altri operandi, PRIMA del prossimo ‘;’ gettone…

  uint32_t* test; test = &__STACK_TOP; for (i = 0; i < 10; i++) { *test++ = 0x5A5A5A5A; } //same as above for (i = 0; i < 10; i++) { *test = 0x5A5A5A5A; test++; } 

Poiché test è un puntatore, test ++ (questo è senza dereferenziamento) incrementerà il puntatore (incrementa il valore del test che è l'indirizzo (di destinazione) di ciò che viene indicato). Poiché la destinazione è di tipo uint32_t, test ++ aumenterà di 4 byte, e se la destinazione fosse per esempio un array di questo tipo, il test ora punterebbe all'elemento successivo. Quando si eseguono questi tipi di manipolazioni, a volte è necessario eseguire prima il cast del puntatore per ottenere l'offset di memoria desiderato.

  ((unsigned char*) test)++; 

Questo incrementerà l'indirizzo di solo 1 byte;)

Da K & R, pagina 105: “Il valore di * t ++ è il carattere che ho indicato prima che t venisse incrementato”.

Se non si utilizza la parentesi per specificare l’ordine delle operazioni, entrambi gli incrementi di prefisso e suffisso hanno precedenza rispetto a riferimento e dereferenziazione. Tuttavia, l’incremento postfisso e prefisso sono due operazioni diverse. In ++ x, l’operatore prende un riferimento alla variabile, ne aggiunge una e la restituisce per valore. In x ++, l’operatore incrementa la variabile, ma restituisce il suo vecchio valore. Si comportano in questo modo:

 //prefix increment (++x) template T operator++(T & x) { x = x + 1; return x; } //postfix increment (x++) template T operator++(T & x, int) //unfortunatelly, the int is how they differentiate { auto temp = x; ++x; return temp; } 

(Si noti che c’è una copia coinvolta nell’incremento postfisso, rendendolo meno efficiente. Ecco perché si dovrebbe prefferizzare ++ i invece di i ++ nei loop.)

Come potete vedere, anche se l’incremento postfisso viene elaborato per primo, a causa del suo comportamento, si sta deferendo il valore precedente del puntatore.

Ecco un esempio:

 char *x = {'a', 'c'}; char y = *x++; char z = *x; 

Il puntatore x verrà incrementato prima della dereferenziazione, ma il dereferenziamento avverrà sul vecchio valore di x (restituito dall’incremento postfisso). Quindi y sarà inizializzato con ‘a’ e z con ‘c’. Nulla sarà diverso se usi una parentesi come questa:

 char *x = {'a', 'c'}; char y = *(x++); char z = *x; 

Ma se lo fai in questo modo:

 char *x = {'a', 'c'}; char y = (*x)++; char z = *x; 

Ora x sarà dereferenziato e il valore puntato da esso (‘a’) verrà incrementato (in ‘b’). Una volta che l’incremento postfisso restituisce il vecchio valore, y verrà comunque inizializzato con ‘a’, ma z verrà anche inizializzato con ‘a’ perché il puntatore non cambia. Infine, se si utilizza il prefisso:

 char *x = {'a', 'c'}; char y = *++x; //or *(++x); char z = *x; 

Ora il dereferenziamento avverrà sul valore incrementato di x, quindi sia y che z saranno inizializzati con ‘a’.

Addendum:

Nella funzione strcpy (menzionata in un’altra risposta), anche l’incremento viene eseguito per primo:

 char *strcpy(char *dst, char *src) { char *aux = dst; while(*dst++ = *src++); return aux; } 

Ad ogni iterazione, src ++ viene elaborato per primo e, essendo un incremento postfix, restituisce il vecchio valore di src. Quindi, il vecchio valore di src (che è un puntatore) è dereferenziato per essere assegnato a qualsiasi cosa si trovi nella parte sinistra dell’operatore di assegnazione. Il dst viene quindi incrementato e il suo vecchio valore è dereferenziato per diventare un lvalue e ricevere il vecchio valore src. Questo è il motivo per cui dst [0] = src [0], dst [1] = src [1] ecc., Fino a quando * dst viene assegnato con 0, interrompendo il ciclo.

Poiché il puntatore viene passato in base al valore, solo la copia locale viene incrementata. Se vuoi veramente incrementare il puntatore devi passarlo in base a questo riferimento:

 void inc_value_and_ptr(int **ptr) { (**ptr)++; (*ptr)++; }