Ponteiro para ponteiro [RESOLVIDO]

1. Ponteiro para ponteiro [RESOLVIDO]

Perfil removido
removido

(usa Nenhuma)

Enviado em 27/05/2014 - 09:29h

Bom dia a todos.

Estou estudando ponteiro para ponteiro. Fazendo alguns exercícios para assimilar o conteúdo, fiz o seguinte código:

#include <stdio.h>

int main(){
    char vogais[]={'a','e','i','o','u'};
    char *p_vogais;
    char **p_Pvogais;
    register int i;
    int tamanho;
    
    p_vogais = vogais;
    p_Pvogais = &p_vogais;
    
    tamanho = sizeof(vogais);
    
    /* Usando o vetor para apresentar os valores na tela */
    for(i = 0; i < tamanho ; i++)
          printf("%c ", vogais[i]);
    
    printf("\n\n");
    /* Usando o ponteiro que aponta para o vetor vogais para 
     * apresentar valores na tela */
    for(i = 0; i < tamanho ; i++)
          printf("%c ", *(p_vogais + i));
     
     printf("\n\n");
     /* Usando o ponteiro para o ponteiro para apresentar os
      * valores na tela */
     for(i = 0; i < tamanho ; i++)
           printf("%c ", **(p_Pvogais + i));
           
     return 0;
} 


É exibido os valores do vetor usando os colchetes, usando o ponteiro também, porém quando chega na parte do ponteiro para ponteiro ocorre falha de segmentação. Eis a saída do programa:


a e i o u  

a e i o u  

Falha de segmentação
 


Por que está ocorrendo isso?



  


2. MELHOR RESPOSTA

Paulo
paulo1205

(usa Ubuntu)

Enviado em 29/05/2014 - 15:34h

Sempre que o assunto é ponteiros, eu fico com vontade de escrever uma aula. Só que falta tempo (além de organização mental para me expressar com a devida clareza e se modo sucinto).

Eu acho que sempre ajuda se você pensar no operador unário * como "o conteúdo da posição de memória dada por". Você pode ler por extenso dessa maneira tanto na hora da declaração quanto na hora da utilização. Veja:

  - a declaração int **ppi; significa, literalmente, que “o conteúdo da posição de memória dada pelo conteúdo da posição de memória dada por ppi é do tipo int” (desde que as respectivas "posições de memória dadas por" sejam endereços válidos no momento em que o programa for executado);
  - a expressão *(*ppi+n) significa “o conteúdo da posição de memória dada por (o conteúdo da posição de memória dada por ppi mais n)”.

Você muitas vezes pode escolher se coloca o foco no ponteiro ou no objeto apontado. Numa declaração como int *pi;, *pi (ou “o conteúdo da posição de memória dada por pi”) é do tipo int, e isso é equivalente a dizer que pi (sem o asterisco) é do tipo “ponteiro para int”. Num caso como o da declaração int **ppi;, **ppi é do tipo int, o tipo de *ppi é “ponteiro para int” e o de ppi é “ponteiro para ponteiro para int”.

Parando mais detidamente sobre a expressão “*(*ppi+n)", acho que ajuda a técnica de "dividir para conquistar". Veja:

  - pelas regras de precedência de operadores, “*(*ppi+n)” é equivalente a “*((*ppi)+n)” (lê-se “o conteúdo de posição de memória dada por ( (o conteúdo da posição de memória dada por ppi) mais n )”;
  - “*ppi”, que é a parte de maior precedência da expressão, devolve um endereço (ponteiro) para um valor do tipo int;
  - ao se somar o valor n ao ponteiro para int obtido acima, obtém-se um novo endereço, que é igual ao endereço anterior mais n vezes o tamanho ocupado por um valor do tipo int (em outras palavras, obtém-se o endereço do (n+1)-ésimo valor inteiro a partir da posição de memória dada por *ppi);
  - ao se aplicar o operador * sobre o endereço obtido, recebe-se o valor do tipo int armazenado nesse endereço.

---

O seu programa usa um ponteiro para ponteiro de modo artificioso, que, na prática, raramente é necessário. Você faz p_Pnum=&p_num;, e depois só usa p_Pnum na forma *p_Pnum; como p_Pnum=&p_num, *p_Pnum=p_num (perceba a identidade comparando o segundo e o terceiro loops de impressão).

---

Uma das razões (e talvez a mais importante) pelas quais ponteiros são importantes em C é para que o valor de uma variável possa ser alterado dentro de uma função e que a alteração seja visível e permanente para quem chamou a função.

O motivo para isso é que C aplica o que se chama de "chamada por valor" ("call by value"), o que significa que, quando uma função é chamada, ela recebe uma cópia dos valores dos argumentos. Caso tenha a pretensão de fazer com que a função manipule diretamente um valor que lhe é externo, a função deve receber não o valor ser manipulado, mas o endereço (na verdade uma cópia do endereço) desse objeto, para que, ao aplicar o operador * sobre o endereço recebido, consiga chegar ao objeto original.

O exemplo clássico é a função swap(), que troca os valores de dois argumentos.

void swap(int *a, int *b){
  int c=*a;
  *a=*b;
  *b=c;
}

int main(void){
  int i=0, j=1;
  swap(&i, &j);  /* Passa (cópias de) os endereços de i e de j. */
  return i;  /* Programa termina com status 1 (i==1). */
} 


Seguindo por essa linha, ponteiros de ponteiros (e ponteiros de ponteiros de ponteiros) costumam aparecer com mais frequência quando uma função tem de manipular o valor de um objeto que já, do lado de fora, um ponteiro. Um exemplo é a função da biblioteca padrão strtol(). O protótipo dela é

long strtol(const char *str, char **endptr, int base) 


onde str indica a string a ser convertida, base indica a base de representação do número, e o "exótico" endptr é um ponteiro para ponteiro, porque prevê que uma variável do tipo “ponteiro para char” será passada por referência (explícita, por meio do operador &), a fim de ser modificada dentro da função, para que aponte para o primeiro caráter dentro da string que não faz parte da representação numérica.

O programa abaixo mostra dois casos de uso de ponteiro para ponteiro: no segundo argumento da chamada a strtol(), aplicando-se o operador & a uma variável que já ponteiro, e a usar um ponteiro para percorrer um array de ponteiros.

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

const char *strings[]={
  "12345",
  "987xxx321",
  "",
  "0",
  "   ",
  "\t -54321 \t",
  NULL
};

int main(void){
  const char **p_number;
  char *eon; /* end of number -- ponteiro para um caráter. */
  long n;

  for(p_number=strings; *p_number!=NULL; p_number++){
    n=strtol(*p_number, &eon, 10);
    if(**p_number){ /* primeiro caráter de *p_number não é nulo. */
      if(!*eon) /* caráter contido no endereço eon é nulo. */
        printf(
          "A conversão de \"%s\" para inteiro consumiu todos os "
          "caracteres e obteve o valor %ld.\n", *p_number, n
        );
      else
        printf(
          "A conversão de \"%s\" para inteiro parou após encontrar "
          "o caráter inesperado '%c'.  O valor da conversão parcial "
          "é %ld.\n", *p_number, *eon, n
        );
    }
    else
      printf(
        "A conversão de \"%s\" para inteiro não foi realizada porque "
        "trata-se de uma string vazia.  Se usado, o valor de n seria "
        "%ld.\n", *p_number, n
      );
  }

  return 0;
} 


3. Re: Ponteiro para ponteiro [RESOLVIDO]

Luis R. C. Silva
luisrcs

(usa Linux Mint)

Enviado em 27/05/2014 - 12:22h

Remova os parênteses nos printf, ficando

*p_vogais + i

e

**p_Pvogais + i


4. Re: Ponteiro para ponteiro [RESOLVIDO]

Perfil removido
removido

(usa Nenhuma)

Enviado em 27/05/2014 - 16:02h

Tirando os parênteses tive a seguinte saída:


a e i o u 

a b c d e 

a b c d e
 


Entretanto o meu objetivo era a saída dos três serem iguais - a, e, i, o, u. Rei_astro, também tenho uma observação a fazer: Se eu tirar os parênteses das expressões primeiramente será considerado o ponteiro e após isso será somado o i.
Por conta disso sempre será analisado primeiramente o endereço do primeiro elemento do vetor - no caso o caractere 'a' - e depois será somado i, pois o * unário tem precedência sobre o +. Por isso a saída fica a, b, c, d, e. No meu caso eu quero que primeiramente ocorra a aritmética, e o resultado da aritmética seja o endereço, por exemplo: *(p_vogais + i) ao invés de *p_vogais + i. Me desculpe se não fui muito claro, mais creio que você entendeu a diferença a qual me refiro.


5. Re: Ponteiro para ponteiro [RESOLVIDO]

Perfil removido
removido

(usa Nenhuma)

Enviado em 27/05/2014 - 16:10h

Consegui um resultado satisfatório:


#include <stdio.h>

int main(){
    char vogais[]={'a','e','i','o','u'};
    char *p_vogais;
    char **p_Pvogais;
    register int i;
    int tamanho;
    
    p_vogais = vogais;
    p_Pvogais = &p_vogais;
    
    tamanho = sizeof(vogais);
    
    /* Usando o vetor para apresentar os valores na tela */
    for(i = 0; i < tamanho ; i++)
          printf("%c ", vogais[i]);
    
    printf("\n\n");
    /* Usando o ponteiro que aponta para o vetor vogais para 
     * apresentar valores na tela */
    for(i = 0; i < tamanho ; i++)
          printf("%c ", *(p_vogais + i));
     
     printf("\n\n");
     /* Usando o ponteiro para o ponteiro para apresentar os
      * valores na tela */
     for(i = 0; i < tamanho ; i++)
           printf("%c ", *(*p_Pvogais + i));
           
     return 0;
}
 


A diferença está na linha 29


Ao invés de ficar assim printf("%c ", **(p_Pvogais + i)), o correto é assim:

                                    printf("%c ", *(*p_Pvogais + i))
 


Mas mesmo assim não entendi completamente. Se fosse usada uma variável comum ao invés de um vetor eu poderia fazer assim:


int a;
int *pa;
int **p_pa;

pa = &a;
p_pa = &pa;

printf("%d %d %d", a, *pa, **p_pa);
 


Funcionaria perfeitamente. Enfim por que no caso dos vetores não posso fazer como acima?



6. Re: Ponteiro para ponteiro [RESOLVIDO]

Luis R. C. Silva
luisrcs

(usa Linux Mint)

Enviado em 27/05/2014 - 17:31h

Já tentou fazer com vetores de inteiros?


7. Re: Ponteiro para ponteiro [RESOLVIDO]

Luis R. C. Silva
luisrcs

(usa Linux Mint)

Enviado em 27/05/2014 - 17:42h

Além disso, seu código só está dando certo por coincidência. A não ser que seja proposital.

O dado tipo char tem 1byte, consequentemente a variável tamanho terá valor 5. Isso não vai servir para vetores de inteiros, que têm 4bytes cada, com um vetor de 5 elementos a variável tamanho ficaria com o valor 20.

Quanto ao parêntese *(*vetor+1) realmente não entendi.


8. Re: Ponteiro para ponteiro [RESOLVIDO]

Igor Morais
igormorais

(usa Gentoo)

Enviado em 27/05/2014 - 21:37h

Se resolveu seu problema, lembre-se de marcar como resolvido.

Viva o Linux !


9. Re: Ponteiro para ponteiro [RESOLVIDO]

Perfil removido
removido

(usa Nenhuma)

Enviado em 28/05/2014 - 07:59h

rei_astro escreveu:

Além disso, seu código só está dando certo por coincidência. A não ser que seja proposital.

O dado tipo char tem 1byte, consequentemente a variável tamanho terá valor 5. Isso não vai servir para vetores de inteiros, que têm 4bytes cada, com um vetor de 5 elementos a variável tamanho ficaria com o valor 20.

Quanto ao parêntese *(*vetor+1) realmente não entendi.


Eu usei o sizeof justamente por ser um vetor de caracteres, se fosse outro tipo as repetições do laço estariam totalmente erradas. Vou testar usando um vetor de int para ver o comportamento do programa.


10. Re: Ponteiro para ponteiro [RESOLVIDO]

Perfil removido
removido

(usa Nenhuma)

Enviado em 28/05/2014 - 09:14h

O mesmo programa com mudança no vetor: mudei de caractere para inteiro:


/* Objetivo do programa: Apresentar os valores do vetor num, usando colchetes,
 * usando ponteiro e usando ponteiro para ponteiro */

#include <stdio.h>

int main(){
    int num[5]={1, 2, 3, 4, 5};
    int *p_num;
    int **p_Pnum;
    register int i;
    
    p_num = num;
    p_Pnum = &p_num;
    
    /* Usando o vetor para apresentar os valores na tela */
    for(i = 0; i < 5 ; i++)
          printf("%d ", num[i]);
    
    printf("\n\n");
    /* Usando o ponteiro que aponta para o vetor vogais para 
     * apresentar valores na tela */
    for(i = 0; i < 5 ; i++)
          printf("%d ", *(p_num + i));
     
     printf("\n\n");
     /* Usando o ponteiro para o ponteiro para apresentar os
      * valores na tela */
     for(i = 0; i < 5 ; i++)
           printf("%d ", *(*p_Pnum + i));
           
     return 0;
}

 


Eis a saída:


1 2 3 4 5 

1 2 3 4 5 

1 2 3 4 5
 


Conclusão: Ficou igual ao código anterior (o que eu usei caracteres), e a saída está correta, esse era meu objetivo. A única parte que me intriga no código é essa na linha 26:


printf("%d ", *(*p_Pnum + i))
 


No caso de vetores somente dessa forma que consegui o resultado esperado, mas não entendi por que tem que ser como na linha acima. Se alguém souber...


11. Re: Ponteiro para ponteiro [RESOLVIDO]

Perfil removido
removido

(usa Nenhuma)

Enviado em 29/05/2014 - 09:31h

Alguém sabe, por favor?


12. Re: Ponteiro para ponteiro [RESOLVIDO]

Perfil removido
removido

(usa Nenhuma)

Enviado em 29/05/2014 - 23:18h

Agora consegui entender o porquê dos ponteiros para ponteiros!

Muito obrigado pela explicação Paulo, e vou procurar tendo como base sua explicação me aprofundar no assunto e saber o momento ideal de usar os ponteiros para ponteiros. Agora entendi o porquê do *(*p_Pnum + i), e como trabalhar com ponteiros para ponteiros. Agradeço a todos pela atenção e pela grande ajuda, e desculpe o tempo tomado!



  



Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts