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