Anda di halaman 1dari 25

Ponteiros e Alocação dinâmica de memória

Acesso direto à memória

❒ Recurso muito poderoso


❒ Nao há como não usá-lo frequentemente

❒ Mas deve-se fazê-lo com muito cuidado

❒ É a principal fonte de erros e são os erros mais difı́cieis de corrigir.


Um erro de uso de mem´ oria pode se manifestar em um local diferente
de onde foi cometido e pode ser intermitente. O programa pode
funcionar (provavelmente com resultados errados) ou pode falhar
(aviso do tipo segmentation fault)
❒ Sem ponteiros não podemos implementar a alocaç˜
ao dinâmica de
mem´oria, entre outras funcionalidades.

etodos Computacionais em F´
ısica I - Ponteiros
1
Ponteiros para variáveis
Podemos acessar um determinado ponto da memória sabendo o seu
endereço
Quando declaramos uma variável,
❒ reservamos uma certa quantidade de memória (depende do tipo)
❒ e associamos o endereço desta memória ao identificador da variável
❒ desta forma podemos acessar o seu valor ou modificá-lo
Ponteiros:
❒ É um novo tipo de variável
❒ Armazena endereços de memória
❒ Há um tipo de ponteiro para cada tipo de variável
ponteiro para int, ponteiro para double, ...

etodos Computacionais em F´
ısica I - Ponteiros
2
❒ O operador unário * indica que um identificador será um ponteiro.
double * pm;
❒ Esta foi a definição do ponteiro. Ele pode conter qualquer lixo até que
um valor lhe seja atribuı́do. Pode até conter o endereço de uma
variável do programa, que não é a que queremos, e se usássemos o
ponteiro por engano, antes de associarmos à variável desejada, isso
acarretaria num erro difı́cil de ser detectado. Para evitar esse erro
zeramos o ponteiro ao defini-lo:
double * pm = NULL;
❒ Para associarmos o ponteiro a uma variável, usamos o operador
unário & que retorna o endereço do identificador ao qual é aplicado:
double m;
double *pm = NULL;
pm = &m;
pm agora contém o endereço de m. Dizemos que pm aponta para m.

etodos Computacionais em F´
ısica I - Ponteiros
3
❒ Diferentes tipos de dados ocupam quantidades diferentes de memória.
O ponteiro além de guardar o endereço do inı́cio de um bloco de
memória associado a uma variável, também guarda a quantidade, a
partir daquele ponto, que foi reservada. Por isto os diferentes tipos de
ponteiro. O seguinte bloco de código resulta em erro:
int i;
double *pd;
pd = &i;
❒ O operador * também permite acessar o conteudo
´ da memória
apontada por um ponteiro:
double m = 5;
double n;
double *pm;
pm = &m;
n = *pm;

etodos Computacionais em F´
ısica I - Ponteiros
4
❒ Podemos declarar um ponteiro do tipo genérico: void *p. Este
ponteiro só guarda o endereço, mas nada sabe sobre o tipo de variável,
isto é, sobre a quantidade de memória alocada. Em certas situações é
conveniente converter (cast) um dado ponteiro para o tipo void.


etodos Computacionais em F´
ısica I - Ponteiros
5
Ponteiros para variáveis
Observemos o exemplo seguinte:
#include <stdio.h>
Resultado:
int main()
pi vale 0x22efc4 e (*pi) vale 0
{ int i = 0;
agora pi vale 0x22efc4 e (*pi) vale 3
int j;
agora pi vale 0x22efc1 e (*pi) vale 7
int *pi = NULL;
pi = &i;
printf("pi vale %p e (*pi) vale %d\n", pi, *pi);
(*pi) = 3;
printf("pi vale %p e (*pi) vale %d\n", pi, *pi);
j=7;
pi = &j;
printf("pi vale %p e (*pi) vale %d\n", pi, *pi);
return 0;
}

etodos Computacionais em F´
ısica I - Ponteiros
6
Ponteiros como argumento de funções
❒ Em C os argumentos de funções são passados por valor. O que fazer se
precisamos alterar um argumento?
Ex.: Uma função que troca os valores de duas variáveis.
❒ Os ponteiros permitem o acesso direto à memória. Dando um ponteiro
para uma variável como argumemto de uma função nos permite mudar
o valor desta variável.


etodos Computacionais em F´
ısica I - Ponteiros
7
m pd p
0x22efc1

5 0x22efc1 0x22efc1
int main(){
double m;
double * pd = NULL;
m = 5;
pd = &m;
mudaMassa(pd);
}
void mudaMassa(double *p){
*p = 8;
return
}


etodos Computacionais em F´
ısica I - Ponteiros
8
void troca( int *i, int *j);
int main()
{
int a=2,b=5;
int *pb=&b;
printf("a=%d b=%d\n",a,b);
troca(&a,pb);
printf("a=%d b=%d\n",a,b);
return 0;
}
void troca( int *i, int *j)
{
int temp=*i;
*i = *j;
*j = temp;
return;
}

etodos Computacionais em F´
ısica I - Ponteiros
9
❒ Observe que os parâmetros formais da função são ponteiros. Os
parâmetros reais são ponteiros ou endereços das variáveis
❒ Para acessarmos os valores das variáveis dentro da função devemos
usar o operador *

❒ Observe que o argumento ainda é passado por valor: O valor do


ponteiro pb, que é o endereço de b não mudou.

❒ Agora podemos entender porque devemos colocar o & quando usamos


a scanf! Ela precisa alterar o conteúdo das variáveis, registrando os
valores lidos do teclado.
❒ Estudar o programa ex ponteiro2.c


etodos Computacionais em F´
ısica I - Ponteiros
10
Ponteiros e arrays
❒ A implementação de arrays em C está bastante interligada com a de
ponteiros, visando facilitar a manipulação dos arrays.
❒ Podemos utilizar a sintaxe normal para fazer um ponteiro apontar para
um elemento de um array:
double *pa, *pb;
double v[5]={1.,3.,7.,4.,6.};
pa = &v[2]; // pa aponta para o elemento 3 de v
pb = &v[4]; // pb aponta para o elemento 5 de v
*pa = 5.; // O valor do elemento 5 de v agora e’ 5
...
❒ Mas podemos utilizar a sintaxe especial para ponteiros e arrays, junto
com as operações para ponteiros
❒ Podemos fazer um ponteiro p apontar para o inı́cio de um array v

etodos Computacionais em F´
ısica I - Ponteiros
11
fazendo p = v;. É a única situação em que o nome do array tem
sentido sem os colchetes. Equivale a fazer p = &v[0];
❒ Podemos usar a sintaxe de arrays com o ponteiro:
Se fizermos p = v; podemos acessar o i-ésimo elemento de v
fazendo p[i]
Mas se fizermos p = &v[3];, p[2] apontará para v[5]!
❒ Podemos fazer algumas operações com ponteiros. Sejam p e q dois
ponteiros:
❒ Somar ou subtrair um inteiro de um ponteiro:
p=p-2; q++; *(p+2) = 3.0;
❒ Subtrair dois ponteiros:
p = p-q; Retorna um inteiro: n´ umero de elementos entre p e q
❒ Comparar dois ponteiros do mesmo tipo:
if(p == q) ...; if(p > q)...;
while( p !=q )... ; ...; if( p != 0 )...

etodos Computacionais em F´
ısica I - Ponteiros
12
❒ Não existe ponteiro para array. O mesmo ponteiro que aponta para
uma variável double pode apontar para um array de doubles. Na
realidade ele aponta para um elemento do array

❒ CUIDADO! A maioria das operações acima só tem sentido quando o


ponteiro aponta para um array. E devemos controlar para não
ultrapassarmos o espaço reservado para o array
❒ Quando somamos 1 a um ponteiro para int ele passa a apontar para o
endereço de memória logo após a memória reservada para este int.
Se somamos 1 a um ponteiro para double ele avança para o endereço
após este double. Por isto ponteiros para ints são diferentes de
ponteiros para doubles

❒ Arrays como argumento de funções:


double modulo( double v[] ) é equivalente a
double modulo (double *p )

etodos Computacionais em F´
ısica I - Ponteiros
13
Ver os programa ex ponteiro3.c


etodos Computacionais em F´
ısica I - Ponteiros
14
Ponteiros para funções
❒ Às vezes precisamos trabalhar com uma função genérica, num trecho
de programa que se aplica a qualquer função de determinado tipo. Para
isto a linguagem C implementa o ponteiro para funço˜es.
❒ Sua declaração inclui o protótipo completo da função:
// Ponteiro para uma funcao que recebe um double
// e retorna um double
double (*f)( double x );
❒ Atenção aos parêntesis em (*f). São necessários pois parêntesis tem
precedência maior. double *f(double) seria um função que
retorna um ponteiro para double!


etodos Computacionais em F´
ısica I - Ponteiros
15
#include <stdio.h>
#include <math.h>
#define PI 3.141592
void tabela( double (*f)( double ), double xmin, double xmax, double dx )
{ double x;
for(x=xmin;x<=xmax;x+=dx) printf("%f %f\n",x,f(x));
}
int main()
{ double (*g)( double );
printf("x(rad) seno(x)\n");
tabela(sin,0.,PI,PI/5);
printf("x(rad) cos(x)\n");
tabela(cos,0.,PI,PI/5);
g = exp;
printf("x exp(x)\n");
tabela( g,0.0, 2.0, 0.2);
return 0;
}


etodos Computacionais em F´
ısica I - Ponteiros
16
Alocação dinâmica de memória
❒ Quando definimos um array devemos dar a sua dimensão (alocação
estática, ou em tempo de compilação)
❒ Em diversas aplicações precisamos trabalhar com arrays de dimensão
variável.Ex.:
1)Programa que calcula a média das notas de uma turma
2)Array de caracteres para armazenar nomes de pessoas
3)Array com as propriedades dos fragmentos criados numa explosão
(número diferente a cada explosão)
❒ Solução simples: Definimos um array de dimensão bem grande e só
usamos o que for necessário
– Desperdı́cio de memória.
– E se ocorrer um caso com mais elementos do que o previsto?
❒ Alocaç˜
ao dinˆ
amica de mem´
oria: A exata quantidade de memória

etodos Computacionais em F´
ısica I - Ponteiros
17
necessária pode ser alocada no momento em que for necessária. O
limite é apenas a memória disponı́vel no computador. Esta memória
pode ( e deve) ser liberada quando não for mais necessária.
❒ Usa o fato de que podemos trabalhar com ponteiros como se fossem
arrays
❒ seja dim a dimensão de um array de doubles que queremos alocar
(pode ser calculado num passo anterior do programa , ou lido do
teclado...). Para alocarmos um array dinamicamente:
1. Incluimos o arquivo de cabecalho correspondente:
#include <stdlib.h>
2. Determinamos a quantidade de memória a ser alocada:
size_t tamanho;
tamanho = sizeof( double ) * dim;
size t é um tipo inteiro adequado para o tamanho do tipo de
dados (ex., double) num certo sistema

etodos Computacionais em F´
ısica I - Ponteiros
18
sizeof é um operador que retorna o tamanho daquele tipo de
dado no sistema
3. Usamos a função malloc para reservar a memória necessária. Ela
retorna um ponteiro genérico para o inı́cio da área reservada.
Devemos convertê-lo (casting) para o tipo que estamos trabalhando
double *p1=NULL;
...
p1= (double *) malloc(tamanho);
if( p1==NULL) { Erro! }
Se não for possı́vel alocar a memória, malloc retorna NULL.
Devemos sempre testar se isto ocorreu.
4. Usamos p normalmente como um array de doubles de dimensão
dim.
5. Quando não precisarmos mais deste array devemos liberar a
memória alocada:

etodos Computacionais em F´
ısica I - Ponteiros
19
free(p1);
p1 = NULL;
Vejamos um exemplo completo
/* Exemplo de alocacao dinamica de memoria
A dimensao do vetor e’ lida em tempo de execucao
*/
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
int main()
{ double *p1=NULL, *p2=NULL;
size_t tamanho;
int dim;
int i;
printf("Entre com a dimensao dos vetores\n");
scanf("%d",&dim);
tamanho = sizeof( double ) * dim;
p1= (double *) malloc(tamanho);
p2= (double *) malloc(tamanho);

etodos Computacionais em F´
ısica I - Ponteiros
20
if( p1==NULL || p2 == NULL ){
printf("Erro: Nao foi possivel alicar a memoria requerida\n");
return 1;
}
printf("Entre com as componentes do vetor\n");
for( i=0;i<dim;i++) scanf("%lf",(p1+i));
printf("\n As componentes sao \n");
for( i=0;i<dim;i++) printf("%f ",p1[i]);

printf("\nEntre com as componentes do vetor\n");


for( i=0;i<dim;i++) scanf("%lf",&p2[i]);
printf("\n As componentes sao \n");
for( i=0;i<dim;i++) {
printf("%f ",*p2 );
p2++; /* Cuidado: O valor de p2 (endereco) foi mudado */
}
free(p1); /* Nao e’ necessario porque o programa ja’ vai terminar */
p1 = NULL;
free(p2-i);
p2 = NULL;

etodos Computacionais em F´
ısica I - Ponteiros
21
return 0;
}

❒ Diferentemente das variáveis automáticas e das estáticas, na alocação


dinâmica a memória permanece disponı́vel do momento em que for
alocada (malloc) até o momento em que for liberada (free) ou o que
programa termine. Pode ser criada numa função e liberada em outra. O
usuário é totalmente responsável pela administração do uso da
memória dinâmica.
❒ Uma área de memória alocada dinamicamente só pode ser acessada
através de ponteiros.
Erros mais comuns no uso de memória dinâmica:
❒ Vazamento de memória: Fazemos o ponteiro direcionado para uma
dada área alocada dinamicamente apontar para outro endereço antes de
liberar a primeira. O endereço da area original se perde, e não
podemos encontrá-la mais. Se isto ocorrer várias vezes

etodos Computacionais em F´
ısica I - Ponteiros
22
(frequentemente o problema está dentro de um laço), podemos acabar
alocando uma grande quantidade de memória sem necessidade
podendo até mesmo esgotar os recursos do sistema.
❒ Ponteiros soltos: Ao liberarmos uma dada área de memória (com
free()), o endereço registrado no ponteiro não é necessariamente
modificado, podendo ainda apontar para o mesmo endereço. Se
usarmos este ponteiro novamente estaremos acessando um área de
memória que pode ainda estar disponı́vel (pura sorte!) ou estar sendo
usada por outras variáveis do nosso programa ou mesmo por outros
programas. Por isso o erro pode se manifestar num lugar diferente de
onde foi cometido. O programa pode ser interrompido ou não, dando
resultados errados. Por isso devemos sempre fazer o ponteiro apontar
para NULL depois de usar o free.
❒ ver escalar com malloc.c


etodos Computacionais em F´
ısica I - Ponteiros
23
Funções que retornam ponteiros

❒ Podemos definir funções que retornam ponteiros. Ex.: É uma maneira


indireta de retornarmos um array!
❒ Isto é útil, mas devemos tomar um certo cuidado: Se a quantidade para
a qual o ponteiro está direcionado foi criada como variável automática
dentro da funçao o ponteiro será inválido, pois variáveis automáticas
são apagadas cada vez que a função termina a sua execução.
❒ Devemos criar esta quantidade usando alocação dinâmica de memória.
Neste caso a área alocada permanece reservada até a liberarmos
explicitamente (o que pode ser feito em qualquer parte do programa
que seja conveniente).
❒ ver programa vetorial.c


etodos Computacionais em F´
ısica I - Ponteiros
24
Tarefa 9
Escreva um programa que ordene os dados contidos no arquivo
amostra2.dat. Seu programa deve:

• Perguntar o nome do arquivo que contém os dados.

• Contar o número de dados (supondo que há um dado por linha no


arquivo de entrada), numa função que retorna o número de dados.

• Usar alocação dinâmica para criar um vetor que guarde todos os dados
do arquivo de entrada.
• Fazer o ordenamento através de uma função do tipo void.

• Usar apenas variáveis locais.


etodos Computacionais em F´
ısica I - Ponteiros
25

Anda mungkin juga menyukai