Pós-incremento em um ponteiro não referenciado?

Tentando entender o comportamento dos pointers em C, fiquei um pouco surpreso com o seguinte (exemplo de código abaixo):

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

Saída:

 64 @ 0xbf84c6b0 63 @ 0xbf84c6b0 

O que exatamente a declaração *our_var_ptr++ na segunda function ( add_one_v2 ) faz, uma vez que claramente não é o mesmo que *our_var_ptr = *our_var_ptr +1 ?

Devido às regras de precedência do operador e ao fato de que ++ é um operador postfix, add_one_v2() faz referência ao ponteiro, mas o ++ está sendo aplicado ao próprio ponteiro . No entanto, lembre-se de que C sempre usa pass-by-value: add_one_v2() está incrementando sua cópia local do ponteiro, que não terá efeito algum sobre o valor armazenado naquele endereço.

Como teste, substitua add_one_v2() por esses bits de código e veja como a saída é afetada:

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

Esta é uma daquelas pegadinhas que tornam C e C ++ muito divertidos. Se você quiser dobrar seu cérebro, descubra este:

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

É uma cópia de string. Os pointers continuam sendo incrementados até que um caractere com valor zero seja copiado. Depois que você souber por que esse truque funciona, você nunca mais esquecerá como o ++ funciona nos pointers novamente.

PS Você pode sempre replace a ordem do operador por parênteses. O seguinte irá incrementar o valor apontado, em vez do próprio ponteiro:

 (*our_var_ptr)++; 

ESTÁ BEM,

 *our_var_ptr++; 

Funciona assim:

  1. A desreferência acontece primeiro, dando-lhe a localização da memory indicada por our_var_ptr (que contém 63).
  2. Então a expressão é avaliada, o resultado de 63 ainda é 63.
  3. O resultado é jogado fora (você não está fazendo nada com isso).
  4. our_var_ptr é então incrementado após a avaliação. Está mudando para onde o ponteiro está apontando, não para o que está apontando.

É efetivamente o mesmo que fazer isso:

 *our_var_ptr; our_var_ptr = our_var_ptr + 1; 

Faz sentido? A resposta de Mark Ransom tem um bom exemplo disso, exceto que ele realmente usa o resultado.

Como os outros apontaram, a precedência do operador faz com que a expressão na function v2 seja vista como *(our_var_ptr++) .

No entanto, como esse é um operador de pós-incremento, não é bem verdade dizer que ele incrementa o ponteiro e, em seguida, o cancela a referência. Se isso fosse verdade, eu não acho que você estaria recebendo 63 como sua saída, uma vez que estaria retornando o valor no próximo local de memory. Na verdade, acredito que a sequência lógica das operações é:

  1. Salva o valor atual do ponteiro
  2. Incrementar o ponteiro
  3. Exclua o valor do ponteiro salvo na etapa 1

Como htw explicou, você não está vendo a mudança no valor do ponteiro porque está sendo passado por valor para a function.

Muita confusão aqui, então aqui está um programa de teste modificado para tornar claro o que acontece (ou pelo 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)); } 

com a saída 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 

Quatro coisas a notar:

  1. alterações na cópia local do ponteiro não são refletidas no ponteiro de chamada.
  2. alterações no destino do ponteiro local afetam o destino do ponteiro de chamada (pelo menos até que o ponteiro de destino seja atualizado)
  3. o valor apontado em add_one_v2 não é incrementado e nem o seguinte é o valor, mas o ponteiro é
  4. o incremento do ponteiro em add_one_v2 acontece após a desreferencia

Por quê?

  • Porque ++ se liga mais fortemente que * (como desreferencia ou multiplicação), então o incremento em add_one_v2 se aplica ao ponteiro, e não ao que ele aponta.
  • post incrementos acontecem após a avaliação do termo, então a desreferência obtém o primeiro valor no array (elemento 0).

our_var_ptr é um ponteiro para alguma memory. ou seja, é a célula de memory onde os dados são armazenados. (neste caso, 4 bytes no formato binário de um int).

* our_var_ptr é o ponteiro não referenciado – vai para o local onde o ponteiro ‘aponta’.

++ incrementa um valor.

assim. *our_var_ptr = *our_var_ptr+1 desreferencia o ponteiro e adiciona um ao valor naquele local.

Agora adicione a precedência do operador – leia-o como (*our_var_ptr) = (*our_var_ptr)+1 e você verá que o desreferenciamento acontece primeiro, então você pega o valor e o incrementa.

Em seu outro exemplo, o operador ++ tem precedência menor que o *, portanto, ele pega o ponteiro que você passou, adicionou um a ele (para que ele aponte para lixo agora) e, em seguida, retorna. (lembre-se que valores são sempre passados ​​por valor em C, então quando a function retorna o ponteiro testvar original permanece o mesmo, você só mudou o ponteiro dentro da function).

Meu conselho, ao usar a desreferenciação (ou qualquer outra coisa), use colchetes para explicitar sua decisão. Não tente se lembrar das regras de precedência, pois você só vai acabar usando outra língua um dia que as tenha um pouco diferente e você ficará confuso. Ou velho e acabe esquecendo qual tem precedência mais alta (como eu faço com * e ->).

O operador ‘++’ tem maior precedência sobre o operador ‘*’, o que significa que o endereço do ponteiro será incrementado antes de ser desreferenciado.

O operador ‘+’, no entanto, tem precedência menor que ‘*’.

Vou tentar responder isso de um ângulo um pouco diferente … Passo 1 Vamos dar uma olhada nos operadores e operandos: Neste caso, é o operando, e você tem dois operadores, neste caso * para desreferenciamento e ++ para incremento. O passo 2, que tem a precedência mais alta, tem maior precedência sobre o passo 3 Onde está o ++, é para a direita, o que significa POST Increment Neste caso, o compilador toma uma ‘nota mental’ para realizar o incremento. com todos os outros operadores … note se ele era * ++ p, então ele vai fazer isso ANTES de, nesse caso, ser equivalente a pegar dois registros do processador, um irá conter o valor do ptereferenciado * p e o outro vai manter o valor do p ++ incrementado, a razão, neste caso, são dois, é a atividade POST … É neste caso que é complicado, e parece uma contradição. Seria de se esperar que o ++ tivesse precedência sobre o *, o que ele faz, apenas que o POST significa que ele será aplicado somente após TODOS os outros operandos, ANTES do próximo ‘;’ símbolo…

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

Como o teste é um ponteiro, o teste ++ (isso sem desreferenciar) incrementará o ponteiro (ele incrementa o valor do teste, que é o endereço (de destino) do que está sendo apontado). Como o destino é do tipo uint32_t, o teste ++ será incrementado em 4 bytes e, se o destino fosse, por exemplo, uma matriz desse tipo, o teste agora estaria apontando para o próximo elemento. Ao fazer esses tipos de manipulações, às vezes você tem que lançar o ponteiro primeiro para obter o deslocamento de memory desejado.

  ((unsigned char*) test)++; 

Isso incrementará o endereço em apenas 1 byte;)

De K & R, página 105: “O valor de * t ++ é o caractere que t apontou antes de t ser incrementado”.

Se você não estiver usando parênteses para especificar a ordem das operações, os incrementos de prefixo e de postfix terão precedência sobre referência e desreferência. No entanto, o incremento de prefixo e prefixo são duas operações diferentes. Em ++ x, o operador toma uma referência à sua variável, adiciona uma a ela e a retorna por valor. Em x ++, o operador incrementa sua variável, mas retorna seu valor antigo. Eles se comportam mais ou menos assim:

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

(Observe que há uma cópia envolvida no incremento de postfix, tornando-a menos eficiente. Essa é a razão pela qual você deveria preferir ++ i em vez de i ++ em loops.)

Como você pode ver, mesmo que o incremento de postfix seja processado primeiro, devido à maneira como ele se comporta, você estará desreferenciando o valor anterior do ponteiro.

Aqui está um exemplo:

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

O ponteiro x será incrementado antes da desreferência, mas a desreferência ocorrerá sobre o valor antigo de x (retornado pelo incremento do postfix). Então y será inicializado com ‘a’ e z com ‘c’. Nada será diferente se você usar parênteses assim:

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

Mas se você fizer assim:

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

Agora x será desreferenciado e o valor apontado por ele (‘a’) será incrementado (para ‘b’). Quando o incremento de postfix retornar o valor antigo, y ainda será inicializado com ‘a’, mas z também será inicializado com ‘a’ porque o ponteiro não foi alterado. Finalmente, se você usar o prefixo:

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

Agora o desreferenciamento acontecerá no valor incrementado de x, então y e z serão inicializados com ‘a’.

Termo aditivo:

Na function strcpy (mencionada em outra resposta), o incremento também é feito primeiro:

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

Em cada iteração, src ++ é processado primeiro e, sendo um incremento de postfix, retorna o valor antigo de src. Então, o valor antigo de src (que é um ponteiro) é desreferenciado para ser atribuído a qualquer coisa que esteja no lado esquerdo do operador de atribuição. O dst é então incrementado e seu valor antigo é desreferenciado para se tornar um lvalue e receber o valor src antigo. É por isso que dst [0] = src [0], dst [1] = src [1] etc, até que * dst seja atribuído com 0, quebrando o loop.

Como o ponteiro está sendo passado por valor, somente a cópia local é incrementada. Se você realmente quiser incrementar o ponteiro, você deve passá-lo por referência como esta:

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