alocação de memoria

1. alocação de memoria

Daniel
danielcrvg

(usa Slackware)

Enviado em 23/12/2014 - 12:52h

Ola pessoal,

estou estudando alocação de memoria baseado nesta lição deste site http://www.cprogressivo.net/2013/10/Funcao-free-Como-liberar-memoria-e-evitar-vazamento.html

no topico: Alocação de memória, free() e segurança

Ai neste topico ele diz que mesmo apos liberar a memoria usando free(), ainda é possivel acessar aquele endereço de memoria e ele da este codigo como exemplo:


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


int main(void)
{
char *senha;

senha = (char *) malloc(21*sizeof(char));
printf("Digite sua senha [ate 20 caracteres]: ");
scanf("%[^\n]s", senha);

printf("Senha: %s\n", senha);
printf("Endereço antes da free(): %d\n", &senha);

free(senha);

printf("Endereço depois da free(): %d\n", &senha);

return 0;
}


Ai eu resolvi ir alem deste exemplo e estou tentando nao somente acessar o endereco, mas o conteudo do endereco da variavel, e fiz a seguinte funcao no codigo:


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

void muda(char *senha){
char *x;
x = senha;
printf("\n\nApontando o endereco dentro da funcao: %d\n",x);
printf("Conteudo: %s\n",*x);
}

int main(void)
{
char *senha;

senha = (char *) malloc(21*sizeof(char));
printf("Digite sua senha [ate 20 caracteres]: ");
scanf("%[^\n]s", senha);
printf("Senha: %s\n", senha);
printf("Endereço antes da free(): %d\n", &senha);
free(senha);
printf("Endereço depois da free(): %d\n", &senha);

muda(&senha);

return 0;
}



So que nao estou conseguindo acessar o conteudo, alguem pode me dizer se é possivel ou nao, e se sim como fazer?


Obrigado,




  


2. Re: alocação de memoria

Alexandre Mulatinho
mulatinho

(usa Slackware)

Enviado em 23/12/2014 - 19:01h

Vejo alguns problemas aí.

1. O endereço da memória é representada no printf por '%p' e não '%d'
2. Você tá criando um ponteiro e passando ele por referência, logo a função muda() teria que ter um parâmetro que é um ponteiro para um ponteiro. (char **)
3. O endereço da memória é único e não é modificado quando entra dentro de uma função.

Uma versão que pegaria seria assim:


mlt@slack:~/codez/c$ cat danielcrvg.c
#include <stdio.h>
#include <stdlib.h>

void muda(char **senha){
char **x;
x = senha;
printf("\nApontando o endereco dentro da funcao: %p\n",x);
printf("Conteudo: %s\n", *x);
}

int main(void)
{
char *senha;

senha = (char *) malloc(21*sizeof(char));
printf("Digite sua senha [ate 20 caracteres]: ");
scanf("%[^\n]s", senha);
printf("Senha: %s\n", senha);
printf("Endereço: %p\n", &senha);

muda(&senha);

free(senha);
return 0;
}



3. Re: alocação de memoria

Daniel
danielcrvg

(usa Slackware)

Enviado em 24/12/2014 - 09:57h

Opa bom dia,


obrigado pelo post.. ainda tenho duvidas nisso..

Por que ao passar por referencia eu tenho que usar um ponteiro para um ponteiro? Tipo eu achava que eu ja poderia passar o ponteiro diretamente..


A outra questao é:

No codigo que vc postou, ele não esta enfatizando o principio do questao, que seria que mesmo apos liberar a memoria com o free() eu ainda possuo aquele endereco, e assim eu queria acessar o conteudo do endereco..

Para exemplificar, invez de colocar o free() no final do codigo, coloca ele antes do muda() pra vc ve.. ele ja nao imprime o conteudo da variavel dentro da funcao..

é isso que eu queria testar, eu ja compreendi que mesmo com o free() o endereco ainda existe, mas eu queria testar se eu consigo acessar o conteudo dela..

saco mais ou menos o que eu quis??


Olha a parte de: Alocação de memória, free() e segurança

http://www.cprogressivo.net/2013/10/Funcao-free-Como-liberar-memoria-e-evitar-vazamento.html


q vc vai entender o que eu quero..



4. Re: alocação de memoria

Alexandre Mulatinho
mulatinho

(usa Slackware)

Enviado em 24/12/2014 - 11:32h

Opa! Bom dia!

Eu não falei que para passar algo por referência você precisa usar um ponteiro para um ponteiro, mas sim que você usou um ponteiro no início para alocar a memória (char *) e então para passar esse ponteiro por referência você não pode usar simplesmente um novo ponteiro e sim um ponteiro para um ponteiro (char **).

Quando você aloca a memória com o malloc() ele reserva um endereço na memória e o retorna para você, se você usar o free() essa memória é liberada e o endereço perdido, então não tem porque você fazer o que você tá querendo porque não vai funcionar.

man malloc
man 3 free

P.S.: Endereço perdido significa que ele está lá mais não está mais acessível pela sua aplicação, isso prova um SEGMENTATION FAULT, olhei o link que você me mandou e parece que você quer fazer peripécias na exploração da memória, mas desse jeito não vai funcionar e não acho que aqui alguém vá ficar ensinando como subir na memória para injetar código :)


5. Re: alocação de memoria

Daniel
danielcrvg

(usa Slackware)

Enviado em 24/12/2014 - 11:45h

nao..

mesmo que vc libere com o free() o endereco nao é perdido..

e é exatamente o que fala o artigo..






6. Re: alocação de memoria

Paulo
paulo1205

(usa Ubuntu)

Enviado em 26/12/2014 - 01:13h

danielcrvg escreveu:

Ola pessoal,

estou estudando alocação de memoria baseado nesta lição deste site http://www.cprogressivo.net/2013/10/Funcao-free-Como-liberar-memoria-e-evitar-vazamento.html

no topico: Alocação de memória, free() e segurança

Ai neste topico ele diz que mesmo apos liberar a memoria usando free(), ainda é possivel acessar aquele endereço de memoria e ele da este codigo como exemplo:

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


int main(void)
{
char *senha;

senha = (char *) malloc(21*sizeof(char));
printf("Digite sua senha [ate 20 caracteres]: ");
scanf("%[^\n]s", senha);

printf("Senha: %s\n", senha);
printf("Endereço antes da free(): %d\n", &senha);

free(senha);

printf("Endereço depois da free(): %d\n", &senha);

return 0;
}


(O assunto do tópico é bem problemático, mas vou deixar essa explicação para o final (em negrito). Primeiro, quero esclarecer outros aspectos dos programas mostrados.)

Esse código está errado.

O primeiro erro é um erro crasso: para imprimir um endereço com printf(), o certo é usar a conversão “%p”, não “%d”. Se você quiser usar a conversão inteira, deve converter o ponteiro para inteiro explicitamente. No entanto, você tem de levar em consideração o tamanho dos tipos de dados, ou a conversão pode não ser válida (por exemplo, numa máquina Intel ou AMD de 64 bits, todos os ponteiros tem tamanho de 64 bits (8 bytes), ao passo que inteiros têm, por default, apenas 32 bits (4 bytes)).

Além disso, se o propósito do programa é demonstrar que free() não altera o valor do ponteiro, ele não deveria ter aplicado o operador & à variável senha. Do jeito como está, o valor mostrado será o mesmo até antes de se chamar malloc() pela primeira vez.

Em outras palavras, o simples fato de se ter declarado uma variável chamada senha faz com que o compilador reserve espaço para ela _durante a compilação_, e é nesse espaço reservado pelo compilador que, _durante a execução_ do programa, será guardado o endereço devolvido por malloc() em decorrência da operação de alocação de um bloco de memória. Então note bem a diferença: a expressão “senha” indica o valor da variável senha, e pode variar ao longo do programa de acordo com manipulações feitas com essa variável; “&senha”, por outro lado, indica o endereço reservado durante a compilação para que a variável senha possa existir, e essa expressão será constante durante todo o tempo de vida da variável, independentemente das manipulações que você fizer sobre ela.

Ai eu resolvi ir alem deste exemplo e estou tentando nao somente acessar o endereco, mas o conteudo do endereco da variavel, e fiz a seguinte funcao no codigo:

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

void muda(char *senha){
char *x;
x = senha;
printf("\n\nApontando o endereco dentro da funcao: %d\n",x);
printf("Conteudo: %s\n",*x);
}

int main(void)
{
char *senha;

senha = (char *) malloc(21*sizeof(char));
printf("Digite sua senha [ate 20 caracteres]: ");
scanf("%[^\n]s", senha);
printf("Senha: %s\n", senha);
printf("Endereço antes da free(): %d\n", &senha);
free(senha);
printf("Endereço depois da free(): %d\n", &senha);

muda(&senha);

return 0;
}


So que nao estou conseguindo acessar o conteudo, alguem pode me dizer se é possivel ou nao, e se sim como fazer?


Você levou mais a frente o erro do programa anterior, e o agravou. Ao passar &senha para o a função muda(), você está passando o endereço _ocupado pela variável senha_, não o endereço _representado por ela_.

Uma forma fácil de identificar o problema é pensar nos tipos envolvidos. Permita-me começar com um exemplo absurdamente primário.

int i;

printf("%d\n", i); /* O tipo de "i" é inteiro. */
printf("%p\n", &i); /* O tipo de "&i" é ponteiro para inteiro. */


Semelhantemente, podemos ter algo como segue.

char c;       /* c guarda um char */
char *pc; /* pc guarda um ponteiro para char */
char **ppc; /* ppc guarda um ponteiro para ponteiro para char */
char ac[10]; /* ac aponta para bloco com dez caracteres */

c='c'; /* OK: variável char recebe valor char. */
pc=c; /* ERRO: ponteiro para char não deve receber valor char */
pc=&c; /* OK: ponteiro para char recebe ponteiro para char */
ppc=pc; /* ERRO: pont. p/ pont. de char não deve receber pont. p/ char */
ppc=&pc; /* OK */
ppc=&(&c); /* ERRO: & deve ser aplicado diretamente sobre variáveis, mas
(&c) não é uma variável, e sim um valor calculado */
c=pc; /* ERRO: char não deve receber ponteiro */
c=*pc; /* OK: char recebe conteúdo apontado por ponteiro para char
(desde que o endereço indicado por pc seja válido!!!) */
pc=*ppc; /* OK: pont. p/ char recebe conteúdo de pont. p/ char
(desde que o valor de ppc seja um endereço válido!!!) */
c=**ppc; /* OK: char receber cont. de cont. de pont. p/ pont. p/ char
(desde que os dois níveis de ponteiros sejam válidos) */
c=ac; /* ERRO: char não deve receber endereço de bloco de chars */
pc=ac; /* OK: pont. p/ char recebe endereço (i.e. pont.) do 1º
elemento do bloco de chars */
ppc=ac; /* ERRO: pont. p/ pont. p/ char não deve receber pont. p/
1º elemento do bloco de chars */
ppc=&ac; /* ERRO: ac não é variável (nomes de arrays em C são como
ponteiros constantes, mas não são lvalues); no entanto,
por uma questão de coerência sintática, em muitos casos
a sintaxe "&nome_do_array" é aceita, ainda que nem sempre
o valor do ponteiro resultante faça muito sentido */
ac=pc; /* ERRO: arrays em C não são variáveis, mas uma expressão
constante, que retorna o endereço do primeiro elemento já
no momento da compilação. */
ac[0]=c; /* OK: elemento do tipo char recebe char. */
*ac=c; /* OK: "*ac" é sinônimo de "ac[0]". */
ac[0]=pc; /* ERRO: elemento char não deve receber ponteiro para char. */
*ac=*pc; /* OK */


NOTA: Muitas das coisas que eu indiquei acima como “ERRO” constituem lógicos ou semânticos, mas não sintáticos. Boa parte delas pode inclusive ser compilada sem alerta do compilador, principalmente se ele for invocado sem opções de diagnóstico ou com opções de compatibilidade com código antiquado. Habilitar opções de diagnóstico e desligar suporte a código legado geralmente faz com que o compilador aponte muitos dos problemas acima -- quiçá todos. No caso do GCC, eu sempre indico as opções "-Wall -Werror -O2 -pedantic" e, dependendo do contexto, a opção que especifica o padrão que deve ser usado durante a compilação ("-std=c90", "-std=c99", "-std=c11", "-std=gnu99" etc.).

Voltando ao seu caso, ao passar um "ponteiro para ponteiro para char" num contexto em que se esperava um "ponteiro para char", você faz com que printf() tente interpretar um endereço como se fosse uma sequência dos caracteres integrantes de um string.


Como corrigir? Altere todas as ocorrências do operador &, removendo-o dessas expressões. Mesmo no programa original, o ponto que se desejava demonstrar ficava escondido por conta da aplicação indevida desse operador.


NOTE PORÉM QUE só se deve tentar usar esse programa em caráter de teste e, mesmo assim, sem garantias de que vá funcionar. Em geral, É UM ERRO TENTAR OBTER ACESSO a dados referenciados por ponteiros depois de as regiões para as quais eles apontavam terem sido liberadas ou devolvidas ao sistema. No Linux, por exemplo, blocos dinamicamente alocados, a partir de um certo tamanho, utilizam as chamadas mmap() e munmap() para alocação e liberação de memória. Quando tais funções são empregadas, a memória liberada é automaticamente retirada do espaço do programa, e qualquer tentativa de examinar conteúdo velho vai provocar uma falha de segmentação. Veja o examplo abaixo.

$ cat > abuso.c <<EOF
#include <stdlib.h>
#include <stdio.h>

int main(void){
int *pi;

pi=malloc(sizeof(int));
if(pi==NULL)
perror("Falha de alocação");

*pi=5;
printf("pi (alocado)=%p; *pi=%d\n", pi, *pi);
free(pi);
printf("ABUSO!!! -- pi (não-alocado)=%p; *pi=%d\n", pi, *pi);

pi=malloc(1000000*sizeof(int));
if(pi==NULL)
perror("Falha de alocação");

*pi=5;
printf("\npi (alocado)=%p; *pi=%d\n", pi, *pi);
free(pi);
printf("ABUSO!!! -- pi (não-alocado)=%p; *pi=%d\n", pi, *pi);

return 0;
}
EOF
$ gcc -Wall -Werror -O2 abuso.c -o abuso
$ ./abuso
pi (alocado)=0x1178010; *pi=5
ABUSO!!! -- pi (não-alocado)=0x1178010; *pi=0

pi (alocado)=0x7fb46762f010; *pi=5
Segmentation fault (core dumped)


No exemplo acima, a primeira alocação é pequena e, por isso, usa o alocador tradicional, usando a chamada brk() e a técnica de listas encadeadas de blocos alocados e blocos livres. Eu esperaria que após a liberação da primeira alocação, o valor do ponteiro pudesse ser lido e fosse preservado, mesmo sendo isso já um abuso. De fato, ele pôde ser, mas note que o valor já foi alterado (imprimiu-se um 0 para *pi, quando eu mesmo esperaria ver um 5). Logo, como você pode ver, já não é verdade que ainda dá para ter acesso aos dados liberados.

Na segunda alocação, eu deliberadamente aloquei um bloco grande, para provocar o uso de mmap()/munmap(). O efeito é que a alocação pega memória do kernel diretamente, e a liberação revoga o acesso a ela imediatamente. Como você pode ver, a tentativa de acesso após a liberação provoca uma falha que mata o programa na hora.



O artigo no qual você se baseou apresenta não um possível caso de uso, mas um flagrante ABUSO de consequências indeterminadas -- pode funcionar, pode corromper uma variável, pode causar a morte do programa, pode corromper o funcionamento do sistema operacional, pode acontecer qualquer coisa. O artigo até tem lá o seu aspecto didático, mas ele JAMAIS deveria ser apresentado a iniciantes em programação -- ainda mais se tomar como verdade que "pode usar um pouquinho de tempo ainda".

Se você está usando esse material como estudo, eis aí um bom caso para você pensar se não deveria trocar de material.



7. Re: alocação de memoria

Paulo
paulo1205

(usa Ubuntu)

Enviado em 26/12/2014 - 03:28h

danielcrvg escreveu:

nao..

mesmo que vc libere com o free() o endereco nao é perdido..

e é exatamente o que fala o artigo..


De fato, o endereço não é perdido. Mas a explicação do artigo e o exemplo mostrado estão errados (se você ler o comentário ao artigo postado por um anônimo no dia 3 de dezembro, vai ver que ele indicou a mesma correção que eu mostrei acima).

O fato é que free() libera a memória indicada por um ponteiro, mas não altera o valor desse ponteiro.

Se você quiser, pode implementar sua própria função de liberação. Algo como vai abaixo.

void my_free(void *p){
/* "Bruxaria" sintático-semântica. Está preparado para isso? */
/* Ah... E eu não garanto que vá funcionar em qualquer tipo de sistema... */
free(*(void **)p);
*(void **)p=NULL;
}

/* ... */

void func(int N){
int *pi;

pi=malloc(N*sizeof *pi);

/* ... bla, bla, bla ... */

my_free(&pi); /* Note que é preciso passar &pi, não somente pi!! */
assert(pi==NULL);
}


Contudo, acho interessante que você saiba que existem alguns motivos para que free() não mexa no valor do ponteiro que lhe foi passado. Eu consigo imaginar alguns cenários:

- Poder receber como argumento um endereço que seja resultado de uma expressão, sem necessariamente estar associado diretamente a uma variável (por exemplo, algo como free(p+5);).

- Poder ter o endereço armazenado numa variável declarada com o atributo constante (const), podendo ser realmente constante (i.e. tentar alterar o valor vai causar erro no programa) ou apenas logicamente constante (o compilador vai reclamar de vir que você está tentando mexer no valor, mas a variável reside em memória que pode ser alterada).

- Poder usar uma variável que, embora não necessariamente declarada no programa em C como constante, resida numa página de memória que tenha sido marcada como read-only pelo sistema operacional.

- Poder utilizar o endereço original em comparações futuras com outros objetos, para ver se continuam válidos (por exemplo: você fez com que diversos elementos de uma lista apontassem para o mesmo lugar, através de cópias do valor de um ponteiro original; quando esse lugar se tornar inválido, você pode varrer a lista e eliminar os elementos que apontem para o endereço que foi liberado).


Um princípio que norteia a criação de bibliotecas é o da generalidade. Os quatro casos acima, ainda que raros na prática, seriam impossíveis de se ter (ou implicariam o (ab)uso de variáveis extras e pointer aliasing) se free() fosse obrigatoriamente equivalente à my_free() que eu mostrei acima. Além disso, em geral não se ganha muita coisa em forçar o ponteiro para NULL após sua liberação, pois ele dificilmente volta a ser usado de novo no código. Nos casos em que isso acontecer -- que são uma minoria -- então o programador pode forçar isso por sua própria conta.


Note ainda que o ponto defendido pelo autor do artigo que você indicou é furado. Limpar o valor do ponteiro não garante que a informação ficará permanentemente inacessível, mas tão-somente que não será acessível através daquela variável. Alguém com acesso a toda a memória do programa (por exemplo, usando um debugger) pode escarafunchar e achar qualquer informação. Aliás, o próprio programa pode acidentalmente aceder sem querer (ou abusivamente) a memória já liberada através de outra variável.

/* Exemplo plausível, mas abusivo.  Use por sua própria conta e risco. */
char *str1, *str2;
str1=malloc(100);
str2=malloc(200);

/*
Grandes chances de str1 e str2 terem sido alocadas em
regiões de memória adjacentes.
*/

free(str2);
printf("%s\n", str1[120]); /* Talvez veja conteúdo de str2 */
free(str1);


O conteúdo sensível pode até acabar parando no disco (imagine o caso em que o programa esteja executando numa máquina com pouca memória, e os dados do programa vão para o swap; quando o programa voltar à RAM, o sistema operacional não vai limpar aquela parte do disco imediatamente e, aliás, pode até nunca mais limpar).

A única proteção real que atribuir NULL a uma variável ponteiro recém-liberada garante é que você vai poder chamar impunemente free() novamente sobre a mesma variável, pois free(NULL) é um no-op.


8. Re: alocação de memoria

Daniel
danielcrvg

(usa Slackware)

Enviado em 26/12/2014 - 08:55h

Opa bom dia Paulo,

muito obrigado pela explicação.

Eu segui aquele artigo sem saber da qualidade, ate mesmo por que me falta conhecimento, e ai ja viu ne.. Se eu nao sei nem pra mim, quanto mais para questionar o trabalho dos outros rs.. Se puder me indicar algum material que posso seguir por favor compartilhe..

Eu entendi a diferenca entre fazer o printf do endereco de memoria, usando '%p' e nao '%d'.

Eu entendi que um vetor de caracteres, por si so ja representa um ponteiro, e que aponta para o primeiro endereco do bloco do char, ou seja de indice '0'.

Quando vc faz esta declaracao:


char ac[10]; /* ac aponta para bloco com dez caracteres */

pc=ac; /* OK: pont. p/ char recebe endereço (i.e. pont.) do 1º
elemento do bloco de chars */
ppc=ac; /* ERRO: pont. p/ pont. p/ char não deve receber pont. p/
1º elemento do bloco de chars */


Por que um ponteiro pode receber o primeiro endereco do bloco, e um pont para pont nao pode?

Outro ponto seria assim:

O operador & indica o endereco daquela variavel na memoria:


int num= 1;
int *pnum;
pnum = # /* Aqui estou apontando para variavel num, passando o endereco dela com o & */


- Se eu quero acessar esta variavel fora da funcao main(), a fim de poder manipular o conteudo dela como eu faria? Eu devo passar assim: funcao(num), funcao(int *num) ou funcao(&num)

- Se ela fosse um char, ao inves de ser um inteiro, a passagem seria do mesmo jeito?

- E após eu dar um free() nela, eu ainda posso manipular o conteudo dela fora da funcao main() ?







9. Re: alocação de memoria

Daniel
danielcrvg

(usa Slackware)

Enviado em 26/12/2014 - 10:43h

danielcrvg escreveu:

Opa bom dia Paulo,

muito obrigado pela explicação.

Eu segui aquele artigo sem saber da qualidade, ate mesmo por que me falta conhecimento, e ai ja viu ne.. Se eu nao sei nem pra mim, quanto mais para questionar o trabalho dos outros rs.. Se puder me indicar algum material que posso seguir por favor compartilhe..

Eu entendi a diferenca entre fazer o printf do endereco de memoria, usando '%p' e nao '%d'.

Eu entendi que um vetor de caracteres, por si so ja representa um ponteiro, e que aponta para o primeiro endereco do bloco do char, ou seja de indice '0'.

Quando vc faz esta declaracao:


char ac[10]; /* ac aponta para bloco com dez caracteres */

pc=ac; /* OK: pont. p/ char recebe endereço (i.e. pont.) do 1º
elemento do bloco de chars */
ppc=ac; /* ERRO: pont. p/ pont. p/ char não deve receber pont. p/
1º elemento do bloco de chars */


Por que um ponteiro pode receber o primeiro endereco do bloco, e um pont para pont nao pode?

Outro ponto seria assim:

O operador & indica o endereco daquela variavel na memoria:


int num= 1;
int *pnum;
pnum = # /* Aqui estou apontando para variavel num, passando o endereco dela com o & */


- Se eu quero acessar esta variavel fora da funcao main(), a fim de poder manipular o conteudo dela como eu faria? Eu devo passar assim: funcao(num), funcao(int *num) ou funcao(&num)

- Se ela fosse um char, ao inves de ser um inteiro, a passagem seria do mesmo jeito?

- E após eu dar um free() nela, eu ainda posso manipular o conteudo dela fora da funcao main() ?



é mais ou menos isso aqui que eu estou tentando:



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

void mudar(char *nome){

int i=0;
printf("\nEntrou na funcao de trocar o conteudo..");
printf("\nEndereco da variavel nome: %p\n",nome);
printf("Conteudo atual da variavel nome: %s\n",nome);
printf("\nTrocando o conteudo da variavel nome..");
char nome2[] = "rafael";
for (i=0;i<strlen(nome);i++) {
*(nome+i) = nome2[i];
}

}

int main(){

char nome[] = "daniel";
char *pnome;
printf("Conteudo da variavel nome: %s\n",nome);
printf("Endereco da variavel nome: %p\n",nome);
pnome = &nome;
printf("\nEndereco para onde o ponteiro pnome esta apontando: %p\n",pnome);
printf("Endereco da ponteiro pnome: %p\n",&pnome);
printf("Conteudo de pnome: %s\n",pnome);

//free(nome); <----- Eu quero acessar o conteudo e altrar ele mesmo dando um free na variavel..

mudar(&nome);
printf("\n\nConteudo da variavel nome trocado: %s\n",nome);

return 0;
}






10. Re: alocação de memoria

Paulo
paulo1205

(usa Ubuntu)

Enviado em 29/12/2014 - 00:52h

danielcrvg escreveu:

Eu entendi que um vetor de caracteres, por si so ja representa um ponteiro, e que aponta para o primeiro endereco do bloco do char, ou seja de indice '0'.


É importante notar algumas diferenças. Um array não representa um ponteiro. Um nome de array carrega consigo, durante a compilação, mais do que simplesmente o primeiro endereço do bloco de memória, mas também informações sobre o tipo de seus elementos e sobre a quantidade de elementos. Outra característica de arrays é que o compilador não cria um espaço junto às variáveis do programa para armazenar o endereço do primeiro elemento: na hora em que reserva espaço para os elementos, o compilador já sabe o endereço desse espaço, de modo que toda ver que aparecer o nome do array, o compilador vai substituí-lo imediatamente com o valor do endereço do primeiro elemento, como se esse valor fosse uma constante do programa (calculada no momento em que o array é declarado). É por isso que um array, após ter sido declarado e/ou definido com seus valores iniciais, não pode receber outros valores ou designar uma outra área (imagine se o compilador deixaria você mudar o valor de uma constante, como, por exemplo, fazer “2=1”!). É por isso também que não faz muito sentido usar o operador & (que serve para obter durante a execução do programa o endereço em que uma variável é armazenada) sobre o nome de um array (imagine se faria sentido dizer “&3.14159265”!).

Ponteiros, por outro lado, são variáveis que armazenam endereços. Mas não qualquer endereço: o compilador carrega informação sobre o tipo de dado para o qual a variável pode apontar junto com ela, de modo a garantir que ela realmente só aponte para objetos desse tipo e não, por acidente, para dados de algum outro tipo, nem se confunda com um valor inteiro qualquer.

De novo: ponteiro é a variável, e seu valor é sempre um endereço. Às vezes até se diz -- e eu infelizmente me incluo entre os que às vezes dizem assim, inclusive nas mensagens que postei acima -- algo do tipo “o valor da expressão é um ponteiro para xyz”, mas isso é tecnicamente impreciso. Melhor seria dizer que “o valor da expressão é o endereço de um objeto xyz”. Agora sim, ponteiros e arrays se parecem um pouco mais: quando são parte de uma expressão, tanto o nome array quanto o de uma variável ponteiro são substituídos pelos respectivos valores (constante no caso de um array, obtido do que está na variável para um ponteiro) e, salvo no caso de a expressão envolver o operador sizeof diretamente sobre o nome, o tipo do valor nessa expressão é, para ambos, justamente “endereço de um objeto xyz”.

É por isso que você pode ter, por exemplo, algo como

char *pc;
char ac[6]="Teste";

pc=ac;


: quando ac aparece no lado direito da atribuição, o nome é substituído pelo valor logicamente constante (porque é um array) cujo tipo é “endereço de objeto do tipo char”, que é justamente o tipo de valor que uma variável do tipo ponteiro para char aceita manipular.

Quando vc faz esta declaracao:

char ac[10];  /* ac aponta para bloco com dez caracteres */

pc=ac; /* OK: pont. p/ char recebe endereço (i.e. pont.) do 1º
elemento do bloco de chars */
ppc=ac; /* ERRO: pont. p/ pont. p/ char não deve receber pont. p/
1º elemento do bloco de chars */


Por que um ponteiro pode receber o primeiro endereco do bloco, e um pont para pont nao pode?


Compatibilidade de tipos. O tipo de valores representados por pc e ac é o mesmo, e é “endereço de objeto do tipo char”. Já ppc espera valores do tipo “endereço de objeto do tipo ‘char *’” (i.e. “endereço de objeto do tipo ponteiro para char”).

Outro ponto seria assim:

O operador & indica o endereco daquela variavel na memoria:

int num= 1;
int *pnum;
pnum = # /* Aqui estou apontando para variavel num, passando o endereco dela com o & */


- Se eu quero acessar esta variavel fora da funcao main(), a fim de poder manipular o conteudo dela como eu faria? Eu devo passar assim: funcao(num), funcao(int *num) ou funcao(&num)


Passagem de argumentos em C é sempre por valor, i.e. o compilador obtém cópias dos valores de cada argumento e as submete para a função invocada. A função pode até alterar os argumentos recebidos, mas como eles são meras cópias, os valores dos objetos originais serão preservados quando a função acabar.

Parte da importância e ubiquidade de ponteiros em C é justamente devido a isso. Quando você passa um valor de endereço como argumento para uma função, mesmo sendo uma mera cópia do valor de uma variável ponteiro ou do endereço constante designado por um nome de array, essa cópia de valor de endereço pode ser usada para chegar ao mesmo dado designado pelo valor de endereço original -- o valor de endereço é o mesmo, dentro ou fora da função. Desse modo, se você aplicar o operador * (lê-se “conteúdo de”) ou o operador de indexação ([]) sobre o endereço recebido, poderá manipular o conteúdo do objeto que reside em tal endereço.

Então, se você tem um objeto designado por num e deseja manipulá-lo noutra função, pode obter o valor do seu endereço expressando &num. Se você assim se expressar ao passar um argumento de função, ela vai receber uma cópia desse valor.

- Se ela fosse um char, ao inves de ser um inteiro, a passagem seria do mesmo jeito?


Sim. O funcionamento é o mesmo para qualquer tipo de objeto apontado.

(Algumas pessoas confundem char e string. Espero que não seja o seu caso.)

- E após eu dar um free() nela, eu ainda posso manipular o conteudo dela fora da funcao main() ?


Você pode brincar com um brinquedo que deu de presente? Pode dirigir um carro que já vendeu? Pode usar uma roupa que jogou no lixo?

O brinquedo dado, o carro vendido e a roupa posta no lixo continuam existindo depois de saírem da sua mão. Então, tecnicamente falando, você até pode conseguir algum meio de usá-los. Mas deveria? Respectivamente, a criança pode chorar, o novo dono do carro pode prestar queixa à polícia, ou você pode acabar doente.

Do mesmo modo, a memória liberada pelo programa continua existindo dentro do computador. Porém, ao liberá-la você diz ao sistema que não a quer mais, e a entrega para que ela faça com ela o uso que bem entender. Se você a quiser, não diga que não a quer. Se disser que não a quer, não pense de novo em querê-la nem tente mexer mais com ela, pois não é mais sua.

Se insistir, esteja preparado para consequências desagradáveis. Acima, eu já mostrei algumas delas.






Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts