Duvida sobre ponteiros

Alguem consegue me explicar oq acontece nessa expressão? **(&P), não sei se um sinal anula outro, porém testando em um programa, vi q **(&P) é equivalente a *P. Porém **(&P+1) não é equivalente a *P+1, por isso não consigo entender essa relação.

Há um certo “perigo” nisso. Não sei qual o compilador nem SO em que vc está trabalhando, muito menos qual é o seu código. Assumi que sejam três variáveis, uma inteira, um ponteiro para inteiro e um ponteiro para um ponteiro de inteiros. De orelhada, tenho praticamente certeza que não há nenhuma garantia que a alocação de memória seja feita de forma contígua, mas no gcc do MinGW (Windows), para o código abaixo, isso parece se aplicar. Mesmo que isso seja uma característica do compilador, você não deveria assumir isso de forma alguma. Outra coisa, isso não tem absolutamente nada a ver com um operador anular o outro. Eu não sei da onde vc tirou isso ai, parece mais prova de concurso ou exercício “idiota” (desculpa o termo kkk).

#include <stdio.h>
#include <stdlib.h>

int main() {

    int a = 50;
    int *b = &a;
    int **c = &b;

    printf( "a:\n" );
    printf( "    endereco: %p\n    valor: %d\n", &a, a );
    printf( "b:\n" );
    printf( "    endereco: %p\n    valor: %p\n"
            "    valor da variavel apontada: %d\n", &b, b, *b );
    printf( "c:\n" );
    printf( "    endereco: %p\n    valor: %p\n"
            "    valor da variavel apontada: %p\n"
            "    valor da variavel apontada pelo ponteiro apontado: %d\n\n", &c, c, *c, **c );

    printf( "**(&c): %p\n", **(&c) );
    printf( "*c: %p\n", *c );
    printf( "(&c+1): %p\n", (&c+1) );
    printf( "**(&c+1): %d\n", **(&c+1) );

    return 0;

}

3 curtidas

Complementando a resposta acima, testei o mesmo código no Ubuntu com gcc 7.5 e **(&c + 1) dá erro (segmentation fault). Também testei em IDE’s online e o resultado foi:

  • no IdeOne.com (com gcc 8.3) também deu erro nesta mesma linha
  • no Repl.it (com clang 7.0), “funciona”

Ou seja, conforme já foi mencionado acima, “não há nenhuma garantia que a alocação de memória seja feita de forma contígua”. Mas vamos ao problema de fato:


A questão é: por que você acha que deveria ser equivalente? Antes de achar algo, você tem que entender o que significa.

Vamos por partes:

&P significa “o endereço de P”. E ao usar *(alguma_coisa), você está dizendo "o valor que está no endereço indicado por alguma_coisa". Então temos que:

  • *(&P) é o valor que está no endereço indicado por &P. E como &P é o endereço de P, então *(&P) é o mesmo que o valor de P (o próprio P)
  • E portanto **(&P) é o valor que está no endereço indicado por *(&P). Mas como *(&P) é equivalente a P, então **(&P) seria o mesmo que *P:
    • Se P for um ponteiro, a expressão pega o valor que estiver no endereço que P contém (ou seja, o mesmo que *P - por isso que é considerado “equivalente”)
    • Se P não for um ponteiro, a expressão não faz sentido, pois é como se quiséssemos fazer *P quando P não é um ponteiro (ou seja, estamos tentando pegar o valor de algo que não é um ponteiro). No meu gcc aqui isso nem compilou (mas pode ser que outros compiladores aceitem, só que aí sabe-se lá o que acontece…)

Não é que um “anulou” o outro, na verdade um operador foi aplicado ao resultado do outro. O & pega o endereço de P, depois o segundo * pega o valor que está nesse endereço (que deve ser outro endereço), e por fim o primeiro * pega o valor que está neste outro endereço.


Agora, o que é &P + 1? É o endereço de P mais 1. Só que aritmética de ponteiros não é tão simples assim, e esse 1 na verdade nem sempre será 1 byte. Quando você soma 1 a &P, a quantidade de bytes adicionada será igual a sizeof(P). Para ver melhor como é isso, fiz o código abaixo:

int a = 50;
int *b = &a;
printf("%ld\n", sizeof(a));
printf( "endereço de a    : %p\n", &a);
printf( "endereço de a + 1: %p\n", &a + 1);
printf("%ld\n", sizeof(b));
printf( "endereço de b    : %p\n", &b);
printf( "endereço de b + 1: %p\n", &b + 1);
char c = 'x';
char *ptr = &c;
printf("%ld\n", sizeof(c));
printf( "endereço de c    : %p\n", &c);
printf( "endereço de c + 1: %p\n", &c + 1);
printf("%ld\n", sizeof(ptr));
printf( "endereço de ptr  : %p\n", &ptr);
printf( "endereço de ptr+1: %p\n", &ptr + 1);

Testei no Ubuntu, gcc 7.5. O resultado foi:

4
endereço de a    : 0x7ffffaca4fa4
endereço de a + 1: 0x7ffffaca4fa8
8
endereço de b    : 0x7ffffaca4fa8
endereço de b + 1: 0x7ffffaca4fb0
1
endereço de c    : 0x7ffffaca4fa3
endereço de c + 1: 0x7ffffaca4fa4
8
endereço de ptr  : 0x7ffffaca4fb0
endereço de ptr+1: 0x7ffffaca4fb8

Repare que quando eu faço &a + 1, ele soma 4 ao valor de &a (pois no meu ambiente sizeof(a) é 4). Mas quando eu faço &b + 1, ele soma 8, já que b é um ponteiro para int, e o sizeof(b) é 8. O mesmo acontece com c (que é um char, cujo sizeof é 1, e por isso &c + 1 soma 1 ao valor de &c). Mas ptr é um ponteiro para char e seu sizeof é 8.


Enfim, vamos destrinchar **(&P + 1):

  • (&P + 1) é o endereço de P mais 1 (seguindo a regra acima, que na verdade não soma necessariamente 1, e sim sizeof(P))
  • *(&P + 1) pega o valor que está no endereço indicado por (&P + 1). Vamos chamar esse valor de P1
  • **(&P + 1) pega o valor que está no endereço indicado por P1. Se P1 for um ponteiro, pega o valor do endereço que ele aponta, senão pode acontecer o que já mencionei acima (dependendo do compilador não compila, ou dá segmentation fault, ou “funciona”, etc)

Agora, e *P + 1? Isso pega o valor que está no endereço apontado por P e soma 1. Se P for, por exemplo, um ponteiro para int, então isso é o valor do int somado a 1. Não é nem de longe equivalente a **(&P + 1). Veja este exemplo:

int a = 1; // a é um inteiro
int *p = &a; // p é um ponteiro com o endereço de a

No código acima temos o ponteiro p, que aponta para o endereço de a.

Se fizermos *p, eu tenho o valor que está no endereço que p aponta. Ou seja, o valor de a, que é 1. Então *p + 1 é igual a 2.

Agora, se eu fizer **(&p + 1):

  • (&p + 1) pega o endereço de p e soma 1 (e já vimos acima que na verdade ele soma sizeof(p), e isso vai apontar para um endereço de memória que não necessariamente será válido - pode por coincidência apontar para algo válido, mas também pode ser que não)
  • *(&p + 1) vai pegar o valor que está no endereço obtido acima (que pode ou não ser válido, então sabe-se lá o que pode vir aqui)
  • **(&p + 1) pega o valor que está no “endereço” obtido no passo anterior (que pode nem ser um endereço de fato - no meu gcc aqui deu segmentation fault, em outros compiladores ou outras circunstâncias pode “funcionar” por coincidência ou devido a detalhes específicos de implementação, etc)

Por isso que **(&P+1) não é equivalente a *P+1.

2 curtidas