victorbrt 0 Denunciar post Postado Julho 15, 2010 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
_Isis_ 202 Denunciar post Postado Julho 15, 2010 *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
Daniloinf 3 Denunciar post Postado Julho 15, 2010 @_Isis_ Aproveitando o tópico, você conhece um bom tutorial para usar o gdb? Compartilhar este post Link para o post Compartilhar em outros sites
_Isis_ 202 Denunciar post Postado Julho 16, 2010 man gdb e o help do programa. Compartilhar este post Link para o post Compartilhar em outros sites
Daniloinf 3 Denunciar post Postado Julho 16, 2010 Excelente! XD Compartilhar este post Link para o post Compartilhar em outros sites
victorbrt 0 Denunciar post Postado Julho 16, 2010 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
guidjos 65 Denunciar post Postado Julho 16, 2010 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
Marcio Bueno 0 Denunciar post Postado Julho 19, 2010 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
guidjos 65 Denunciar post Postado Julho 19, 2010 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
Marcio Bueno 0 Denunciar post Postado Julho 19, 2010 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