Ir para conteúdo

POWERED BY:

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

victorbrt

passagem de referências (**p)

Recommended Posts

Olá pessoal,

 

Sou novo aqui no fórum e venho com uma dúvida.

 

Já tenho uma boa noção de C e como funcionam os ponteiros, e também de passagem por valor e por referência numa função, que quando a passagem e por valor a variável original não é modificada dentro da função, apenas e copiada para a função e que quando a passagem e por referência (que acontece com vetores, matrizes, registros e usando ponteiros) o endereço da váriavel original é passado e modificado diretamente na função ou seja, o valor original é modificado.

 

Até aí tudo bem. Eu consegui entender perfeitamente o conceito da passagem por referência de ponteiros, como neste exemplo bobo (coloquei comentários):

 

 

alteraValor (int *num){ // o endereco da variavel x é recebido no ponteiro num, que agora aponta pra x
     *num = 10; // o ponteiro num modifica o valor de x para 10, pois ele aponta pra x
}

int main(){
    int x = 5;
    alteraValor(&x); // o endereço da variavel x é passado para a funcao (mais especificamente, para o ponteiro da lista de parametros)
    return 0;
}

O que eu não consigo entender, de jeito nenhum, é a passagem da referência para o conteúdo de uma referência. Exemplo:

 

void alteraValor (int **x, int *y){ // o que **x está recebendo do endereco de x, quando se declara assim?
     *x = *y;
}

int main(){
    int *y = 25;
    int *y = 25;
    alteraValor (&x, &y);
    return 0;
}

O que acontece quando se passa, pra uma função, **x, qual a diferença para *x? O ponteiro não contém apenas um endereço? Eu não consigo ver a diferença entre os dois. Pra onde **x está apontando?

Eu vi isso em um exemplo de lista simplesmente encadeada (estou me adiantando, já que vou ter Estrutura de Dados em C no 2 semestre), e a cabeca (que é um ponteiro struct que aponta pro registro de produtos), na funcao de inserir novos nós na lista, estava declarada como **cabeca. Foi aí que não entendi o porque. Pra quem quiser dar uma olhada no exemplo, segue: http://www.inf.ufg.br/~vagner/pub/CursoC/c.html.

 

Agradeço qualquer ajuda.

Compartilhar este post


Link para o post
Compartilhar em outros sites

*x => x[]

**x => x[][]

 

Se você consegue entender que *x é uma lista de endereços de alguma coisa, então não deve ser difícil deduzir que **x é uma lista contendo os endereços iniciais de listas de endereços de alguma coisa.

 

É bom você aprender a usar as opções do compilador p/ ver possíveis warnings e um depurador p/ visualizar endereços de memória.

 

ponteiro.c: In function ‘alteraValor’:

ponteiro.c:2: warning: assignment makes pointer from integer without a cast

ponteiro.c: In function ‘main’:

ponteiro.c:6: warning: initialization makes pointer from integer without a cast

ponteiro.c:7: warning: initialization makes pointer from integer without a cast

ponteiro.c:8: warning: passing argument 2 of ‘alteraValor’ from incompatible pointer type

 

 

#include <stdio.h>
#include <stdlib.h>
void exibeValores1(int x){
printf("Dado armazenado: %d \t End. memória: %p\n", x, &x);
}

void exibeValores2(int *x) {
printf("Dado armazenado: %d \t End. memória: %p\n", *x, &x);
}

int main(){
int x = 5;
exibeValores1(x);
exibeValores2(&x);

int *y = (int *)malloc(sizeof(int)); // Atribuição de um endereço de memória ao ponteiro.
(*y) = 8;
free(y);

int **z = (int **)malloc(sizeof(int*));
(*z) = (int *)malloc(sizeof(int));
(**z) = 3;
free(z);
return 0;
}

 

 

isis@linux-ke4t:~/src/duvidas.imasters> ./a.out

Dado armazenado: 5 End. memória: 0xbfc17410

Dado armazenado: 5 End. memória: 0xbfc17410

 

Starting program: /home/isis/src/duvidas.imasters/a.out

Dado armazenado: 5 End. memória: 0xbfffee90

Dado armazenado: 5 End. memória: 0xbfffee90

 

Breakpoint 1, main () at mem.c:19

19 int *y = (int *)malloc(sizeof(int)); // Atribuição de um endereço de memória ao ponteiro.

(gdb) s

 

Breakpoint 2, main () at mem.c:20

20 (*y) = 8;

(gdb) s

 

Breakpoint 3, main () at mem.c:21

21 free(y);

(gdb) p y

$4 = (int *) 0x804b008

(gdb) p *y

$5 = 8

(gdb) s

 

Breakpoint 4, main () at mem.c:23

23 int **z = (int **)malloc(sizeof(int*));

(gdb) s

 

Breakpoint 5, main () at mem.c:24

24 (*z) = (int *)malloc(sizeof(int));

(gdb) s

 

Breakpoint 6, main () at mem.c:25

25 (**z) = 3;

(gdb) s

 

Breakpoint 7, main () at mem.c:26

26 free(z);

(gdb) p z

$6 = (int **) 0x804b008

(gdb) p *z

$7 = (int *) 0x804b018

(gdb) p **z

$8 = 3

 

 

Se você trocar (*y) = 8 por y = 8, vai ter

 

(gdb) p y

$1 = (int *) 0x8

 

 

Ou seja, o conteúdo da variável y é o endereço de memória 8, que tem acesso proibido:

 

(gdb) p *y

Cannot access memory at address 0x8

 

 

 

O asterisco, então, é o modo de dizer que você não quer manipular o conteúdo da variável y (um endereço de memória), mas o conteúdo desse endereço armazenado na variável y (um local anônimo em memória).

 

 

#include <stdio.h>
#include <stdlib.h>
void exibeValores1(int x){
printf("Dado armazenado: %d \t End. memória: %p\n", x, &x);
}

void exibeValores2(int *x) {
printf("Dado armazenado: %d \t End. memória: %p\n", *x, &x);
}

void exibeValores3(int **x) {
printf("End. mem. 1 (da variável): %p \t End. mem. 2 (armazenado na variável): %p \t Dado armazenado em localização anônima: %d\n", x, (*x), (**x));
}

int main(){
int x = 5;
exibeValores1(x);
exibeValores2(&x);

int **z = (int **)malloc(sizeof(int*));
(*z) = (int *)malloc(sizeof(int));
(**z) = 3;

exibeValores3(z);
free(z);
return 0;
}

 

 

Starting program: /home/isis/src/duvidas.imasters/a.out

Dado armazenado: 5 End. memória: 0xbfffee90

Dado armazenado: 5 End. memória: 0xbfffee90

 

Breakpoint 1, main () at mem2.c:20

20 int **z = (int **)malloc(sizeof(int*));

(gdb) s

 

Breakpoint 2, main () at mem2.c:21

21 (*z) = (int *)malloc(sizeof(int));

(gdb) s

 

Breakpoint 3, main () at mem2.c:22

22 (**z) = 3;

(gdb) s

 

Breakpoint 4, main () at mem2.c:24

24 exibeValores3(z);

(gdb) p z

$1 = (int **) 0x804b008

(gdb) p *z

$2 = (int *) 0x804b018

(gdb) p **z

$3 = 3

(gdb) s

exibeValores3 (x=0x804b008) at mem2.c:12

12 printf("End. mem. 1 (da variável): %p \t End. mem. 2 (armazenado na variável): %p \t Dado armazenado em localização anônima: %d\n", x, (*x), (**x));

(gdb) s

End. mem. 1 (da variável): 0x804b008 End. mem. 2 (armazenado na variável): 0x804b018 Dado armazenado em localização anônima: 3

 

 

 

Se alguém percebeu algum erro, corrija, porque é mais fácil entender ponteiros usando e desenhando que só lendo sobre eles.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Bom, acho que agora finalmente consegui entender. Depurei uma lista encadeada e vi exatamente o que estava acontecendo.

 

Quando se declara uma variavel **, significa que ela pode apontar pra dois lugares: o primeiro ponteiro aponta pra região da memória alocada e o segundo, aponta/acessa o conteúdo dessa região da memória. Por isso que é a "refêrencia da referência".

 

Seria isso? Ou ainda estou enganado?

 

Quanto ao meu segundo código, realmente dá erro. Fui testar aqui no CodeBlocks e o compilador acusou as mesmas coisas (na hora que postei estava em um PC sem nenhuma IDE). Vendo direito o código, fica óbvio porque não funciona, os ponteiros estão em uma região aleatória da memória. Fiz apenas como exemplo, hehehe.

 

EDIT: aliás, há muitos mais erros no segundo código. Tanto na funcao como na passagem de argumentos. Consegui compilar ele e tive que fazer diversas modificações.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Uma abstração válida é a que a _Isis_ usou: a de que um ponteiro denota uma lista. Então:

 

char *lista;

Denota uma lista (*) de (char)s. Enquanto que

 

char **lista;

Denota uma lista (*) de (char *)s. Ou seja, uma lista de listas de (char). Cada posição de char **lista contém uma char *lista.

 

... e assim por diante.

 

char *lista;

lista: * -> [][][][][]...

char **lista;

lista: ** -> * -> * -> * ...
             |    |    |
             v    v    v
            [ ]  [ ]  [ ]
            [ ]  [ ]  [ ]
            ...  ...  ...

Quando você passa uma variável por referência (call(&var)), você fornece o endereço de memória daquela variável à função que a utiliza, ao invés de uma cópia do seu valor. A partir deste endereço, você consegue derreferenciar o ponteiro-parâmetro (usando *) para alterar o valor apontado pela posição "de fato" da variável em questão.

 

Ficou claro?

Compartilhar este post


Link para o post
Compartilhar em outros sites

Quando você passa uma variável por referência (call(&var)), você fornece o endereço de memória daquela variável à função que a utiliza, ao invés de uma cópia do seu valor. A partir deste endereço, você consegue derreferenciar o ponteiro-parâmetro (usando *) para alterar o valor apontado pela posição "de fato" da variável em questão.

 

Eu acho que seria melhor explicar que você passa o endereço de memória da variável, pois C é uma linguagem que não tem passagem por referência, e é utilizando ponteiros que se simula uma passagem por referência.

 

Outras linguagens, como C++, realmente tem passagem por referência e não precisam deste artifício, mas em C toda passagem é por valor.

 

Se você quer alterar o valor de uma variável dentro de uma função é necessário passar o endereço desta variável (ponteiro) e dereferenciá-lo, como no exemplo abaixo:

 

tipo1 minhaFuncao1(tipo2 *parametro1)
{
  ...
  *parametro1 = novoValor;
  ...
}

Agora, se você precisa que um ponteiro passado como parâmetro para uma função aponte para outra posição de memória, faz-s necessário uma nova indireção, isto é, ao invés de utilizar um ponteiro, você precisará utilizar um ponteiro para ponteiro:

 

tipo1 minhaFuncao2(tipo2 **parametro1)
{
  ...
  *parametro1 = novoValor; // sabendo que novoValor é um endereço
  ...
}

Neste exemplo acima (minhaFuncao2), o efeito desejado é fazer com que a variável passada como parâmetro passe a apontar para uma nova posição de memória, o que não seria possível com o primeiro código (minhaFuncao1).

 

Como agora você uma nova indireção é possível tanto alterar para onde o ponteiro está apontando, quando é possível alterar o valor para onde o ponteiro atual está apontando:

 

tipo1 minhaFuncao2(tipo2 **parametro1)
{
  ...
  *parametro1 = novoValor; // faz o ponteiro apontar para uma nova posição de memória
  **parametro1 = novoValor2; // altera o conteúdo apontado por *parametro1
  ...
}

 

Você também pode dar uma olhada nestes dois sites: Curso de C e Como Tudo Funciona que têm exemplos de ponteiro para ponteiro.

Compartilhar este post


Link para o post
Compartilhar em outros sites

 

Quando você passa uma variável por referência (call(&var)), você fornece o endereço de memória daquela variável à função que a utiliza, ao invés de uma cópia do seu valor. A partir deste endereço, você consegue derreferenciar o ponteiro-parâmetro (usando *) para alterar o valor apontado pela posição "de fato" da variável em questão.

 

Eu acho que seria melhor explicar que você passa o endereço de memória da variável(...)

Você leu o que você citou? De novo:

 

Quando você passa uma variável por referência (call(&var)), você fornece o endereço de memória daquela variável à função que a utiliza, (...)

 

Compartilhar este post


Link para o post
Compartilhar em outros sites

Quando você passa uma variável por referência (call(&var))

O que eu chamei a atenção foi que: C não possui passagem por referência...

 

 

Acho que você não leu o que você escreveu...

Compartilhar este post


Link para o post
Compartilhar em outros sites

×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.