¿Incremento posterior en un puntero desreferenciado?

Al tratar de comprender el comportamiento de los punteros en C, me sorprendió un poco lo siguiente (código de ejemplo a continuación):

#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 */ } 

Salida:

 64 @ 0xbf84c6b0 63 @ 0xbf84c6b0 

¿Qué hace exactamente la *our_var_ptr++ en la segunda función ( add_one_v2 ) ya que claramente no es lo mismo que *our_var_ptr = *our_var_ptr +1 ?

Debido a las reglas de precedencia del operador y al hecho de que ++ es un operador de postfijo, add_one_v2() desreferencia el puntero, pero ++ se está aplicando realmente al puntero . Sin embargo, recuerde que C siempre usa pass-by-value: add_one_v2() está incrementando su copia local del puntero, lo que no tendrá ningún efecto en el valor almacenado en esa dirección.

Como prueba, reemplace add_one_v2() con estos bits de código y vea cómo se ve afectada la salida:

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

Este es uno de esos pequeños gotcha que hacen que C y C ++ sean tan divertidos. Si quieres doblegar tu cerebro, piensa en este:

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

Es una copia de cadena. Los punteros se siguen incrementando hasta que se copia un carácter con un valor de cero. Una vez que sepa por qué funciona este truco, nunca olvidará cómo funciona ++ en los punteros nuevamente.

PD Siempre puede anular el orden del operador con paréntesis. Lo siguiente incrementará el valor apuntado a, en lugar del puntero en sí:

 (*our_var_ptr)++; 

DE ACUERDO,

 *our_var_ptr++; 

funciona así:

  1. La desreferencia ocurre primero, proporcionándole la ubicación de memoria indicada por our_var_ptr (que contiene 63).
  2. Luego se evalúa la expresión, el resultado de 63 sigue siendo 63.
  3. El resultado se descarta (no está haciendo nada con eso).
  4. our_var_ptr luego se incrementa DESPUÉS de la evaluación. Está cambiando hacia dónde apunta el puntero, no hacia dónde apunta.

En realidad es lo mismo que hacer esto:

 *our_var_ptr; our_var_ptr = our_var_ptr + 1; 

¿Tener sentido? La respuesta de Mark Ransom tiene un buen ejemplo de esto, excepto que realmente usa el resultado.

Como han señalado los otros, la precedencia del operador hace que la expresión en la función v2 se vea como *(our_var_ptr++) .

Sin embargo, como se trata de un operador de incremento posterior, no es del todo cierto decir que incrementa el puntero y luego lo desreferencia. Si esto fuera cierto, no creo que obtenga 63 como salida, ya que devolvería el valor en la siguiente ubicación de memoria. En realidad, creo que la secuencia lógica de operaciones es:

  1. Ahorre el valor actual del puntero
  2. Incrementa el puntero
  3. Dereference el valor del puntero guardado en el paso 1

Como lo explicó htw, no está viendo el cambio en el valor del puntero porque se pasa por valor a la función.

Mucha confusión aquí, así que aquí hay un progtwig de prueba modificado para hacer que lo que pase sea claro (o al menos claro):

 #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 el resultado resultante:

 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 

Cuatro cosas a tener en cuenta:

  1. los cambios en la copia local del puntero no se reflejan en el puntero de llamada.
  2. los cambios en el destino del puntero local afectan al objective del puntero de llamada (al menos hasta que se actualice el puntero de destino)
  3. el valor apuntado en add_one_v2 no se incrementa y tampoco tiene el siguiente valor, pero el puntero es
  4. el incremento del puntero en add_one_v2 ocurre después de la desreferencia

¿Por qué?

  • Porque ++ une más estrechamente que * (como desreferencia o multiplicación), por lo que el incremento en add_one_v2 aplica al puntero y no a lo que apunta.
  • los incrementos posteriores ocurren después de la evaluación del término, por lo que la desreferencia obtiene el primer valor en la matriz (elemento 0).

our_var_ptr es un puntero a algo de memoria. es decir, es la celda de memoria donde se almacenan los datos. (n este caso 4 bytes en el formato binario de un int).

* our_var_ptr es el puntero desreferenciado: va a la ubicación en la que apunta el puntero.

++ incrementa un valor.

asi que. *our_var_ptr = *our_var_ptr+1 elimina la referencia del puntero y agrega uno al valor en esa ubicación.

Ahora agregue la precedencia del operador, léala como (*our_var_ptr) = (*our_var_ptr)+1 y verá que la desreferencia ocurre primero, por lo tanto, toma el valor e increméntelo.

En el otro ejemplo, el operador ++ tiene una prioridad menor que el *, por lo que toma el puntero que ingresó, le agregó uno (por lo que apunta a la basura ahora) y luego regresa. (Recuerde que los valores siempre se pasan por valor en C, de modo que cuando la función retorna, el puntero testvar original permanece igual, solo cambió el puntero dentro de la función).

Mi consejo es que al usar la desreferenciación (o cualquier otra cosa) use corchetes para que su decisión sea explícita. No trates de recordar las reglas de precedencia, ya que solo terminarás usando otro idioma un día que los diferencie un poco y te confundirás. O viejo y termina olvidando cuál tiene mayor precedencia (como hago con * y ->).

El operador ‘++’ tiene mayor precedencia sobre el operador ‘*’, lo que significa que la dirección del puntero se incrementará antes de desreferenciarse.

Sin embargo, el operador ‘+’ tiene una prioridad menor que ‘*’.

Trataré de responder esto desde un ángulo diferente … Paso 1 Veamos los operadores y los operandos: en este caso es el operando, y usted tiene dos operadores, en este caso * para desreferenciar y ++ para el incremento El paso 2 que tiene la precedencia más alta ++ tiene una precedencia mayor que * Paso 3 Donde está ++, está a la derecha lo que significa Incremento POST En este caso, el comstackdor toma una “nota mental” para realizar el incremento DESPUÉS de que está hecho con todos los demás operadores … fíjate si era * ++ p, entonces lo hará ANTES, entonces en este caso, es equivalente a tomar dos del registro del procesador, uno mantendrá el valor de la referencia desreferenciada * p y el otro sostendrá el valor del p ++ incrementado, la razón en este caso son dos, es la actividad POST … Aquí es donde en este caso es complicado, y parece una contradicción. Uno esperaría que el ++ tenga prioridad sobre el *, lo que hace, solo que el POST significa que se aplicará solo después de TODOS los demás operandos, ANTES del siguiente ‘;’ simbólico…

  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++; } 

Debido a que la prueba es un puntero, test ++ (esto es sin desreferenciarlo) incrementará el puntero (incrementa el valor de la prueba que pasa a ser la dirección (destino) de lo que se apunta). Debido a que el destino es de tipo uint32_t, la prueba ++ se incrementará en 4 bytes, y si el destino fuera, por ejemplo, una matriz de este tipo, entonces la prueba estaría apuntando al siguiente elemento. Al realizar este tipo de manipulaciones, a veces debe lanzar el puntero para obtener el desplazamiento de memoria deseado.

  ((unsigned char*) test)++; 

Esto incrementará la dirección solo en 1 byte;)

De K & R, página 105: “El valor de * t ++ es el carácter que apuntó antes de que t se incrementara”.

Si no usa paréntesis para especificar el orden de las operaciones, tanto los incrementos de prefijo como de postfijo tienen prioridad sobre la referencia y la desreferencia. Sin embargo, postfix y el incremento de prefijo son dos operaciones diferentes. En ++ x, el operador toma una referencia a su variable, agrega una a ella y la devuelve por valor. En x ++, el operador incrementa su variable, pero devuelve su valor anterior. Se comportan de esta manera:

 //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; } 

(Tenga en cuenta que hay una copia involucrada en el incremento de postfijo, lo que hace que sea menos eficiente. Esa es la razón por la que debe preferir ++ i en lugar de i ++ en bucles).

Como puede ver, aunque el incremento de postfix se procese primero, debido a la forma en que se comporta, eliminará la referencia del valor anterior del puntero.

Aquí hay un ejemplo:

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

El puntero x se incrementará antes de la desreferencia, pero la desreferencia ocurrirá sobre el valor anterior de x (devuelto por el incremento del postfijo). Entonces, y se inicializará con ‘a’, y z con ‘c’. Nada será diferente si usa paréntesis como este:

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

Pero si lo haces así:

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

Ahora x será desreferenciado y el valor apuntado por él (‘a’) se incrementará (a ‘b’). Una vez que el incremento de postfijo devuelve el valor anterior, y todavía se inicializará con ‘a’, pero z también se inicializará con ‘a’ porque el puntero no cambió. Finalmente, si usa el prefijo:

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

Ahora la desreferencia ocurrirá en el valor incrementado de x, de modo que tanto y como z se inicializarán con ‘a’.

Apéndice:

En la función strcpy (mencionada en otra respuesta), el incremento también se hace primero:

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

En cada iteración, src ++ se procesa primero y, al ser un incremento de postfijo, devuelve el valor anterior de src. Luego, el antiguo valor de src (que es un puntero) se desreferencia para asignarlo a lo que esté en el lado izquierdo del operador de asignación. El dst luego se incrementa y su valor anterior se desreferencia para convertirse en un lvalue y recibir el antiguo valor de src. Es por esto que dst [0] = src [0], dst [1] = src [1] etc., hasta que * dst se asigne con 0, rompiendo el bucle.

Como el puntero se pasa por valor, solo se incrementa la copia local. Si realmente desea incrementar el puntero, debe pasarlo por referencia de esta manera:

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