Você está na página 1de 14

8.

Endereos e Ponteiros

Neste captulo veremos a definio e os principais usos de ponteiros. Veremos as operaes


fundamentais como ponteiros , a estreita relao de ponteiros vetores e strings e ainda a alocao dinmica
de memria e a passagem de funes para funes com o uso de ponteiros..

9.1 Introduo
Toda informao (dado armazenado em varivel simples ou vetor) que manipulamos em um
programa est armazenado na memria do computador. Cada informao representada por um certo
conjunto de bytes (Ver captulo 2). Por exemplo: caracter (char): 1 byte, inteiro (int): 2 bytes, etc.
Cada um destes conjuntos de bytes, que chamaremos de bloco, tem um nome e um endereo de
localizao especifica na memria.
Exemplo: Observe a instruo abaixo:
int num = 17;
ao interpretar esta instruo, o processador pode especificar:
Nome da informao: num
Tipo de informao: int
Tamanho do bloco (nmero de bytes ocupados pela informao): 2
Valor da informao: 17
Endereo da informao (localizao do primeiro byte): 8F6F:FFF2 (hexadecimal)
Em geral, interessa ao programador apenas os nomes simblicos que representam as informaes,
pois com estes nomes que so realizadas as operaes do seu algoritmo. Porm, ao processador interessa
os endereos dos blocos de informao pois com estes endereos que vai trabalhar.
Programa Exemplo: O arquivo e0801.cpp contm um programa com instrues para
inspecionar o endereo de uma varivel, usando o recurso Inspect do Turbo C++. Observe que o endereo
mostrado corresponde ao primeiro byte do bloco, mesmo que o bloco ocupe mais de um byte: No caso, um
float ocupa um bloco de 4 bytes.

89

8.2 Ponteiros
Ponteiros so variveis que contm endereos. Neste sentido, estas variveis apontam para algum
determinado endereo da memria. Em geral, o ponteiro aponta para o endereo de alguma varivel
declarada no programa.
8.2.1

Declarao de ponteiros.

Quando declaramos um ponteiro, devemos declar-lo com o mesmo tipo (int, char, etc.) do bloco a
ser apontado. Por exemplo, se queremos que um ponteiro aponte para uma varivel int (bloco de 2 bytes)
devemos declar-lo como int tambm.
Sintaxe: A sintaxe da declarao de um ponteiro a seguinte:
tipo_ptr *nome_ptr_1;
ou
tipo_ptr* nome_ptr_1, nome_ptr_2, ...;

onde:
tipo_ptr : o tipo de bloco para o qual o ponteiro apontar.
* : um operador que indica que nome_ptr um ponteiro.
nome_ptr_1, nome_ptr_2,...: so os nomes dos ponteiros (os nomes dos ponteiros
obedecem as mesmas regras da seo 2.2.1)
Exemplo: Veja as seguintes instrues:
int *p;
float* s_1, s_2;
A primeira instruo declara um ponteiro chamado p que aponta para um inteiro. Este ponteiro
aponta para o primeiro endereo de um bloco de dois bytes. Sempre necessrio declarar o tipo do
ponteiro. Neste caso dizemos que declaramos um ponteiro tipo int.
A segunda instruo declara dois ponteiros (s_1 e s_2) do tipo float. Observe que o * est
justaposto ao tipo: assim todos os elementos da lista sero declarados ponteiros.

8.2.2

90

Operadores & e *

Quando trabalhamos com ponteiros, queremos fazer duas coisas basicamente:


conhecer endereo de uma varivel;
conhecer o contedo de um endereo.

Para realizar estas tarefas a linguagem C nos providencia dois operadores especiais:
o operador de endereo: &
o operador de contedo: *

O operador de endereo (&) determina o endereo de uma varivel (o primeiro byte do bloco
ocupado pela varivel). Por exemplo, &val determina o endereo do bloco ocupado pela varivel val.
Esta informao no totalmente nova pois j a usamos antes: na funo scanf().
Exemplo: Quando escreve-se a instruo:
scanf("%d", &num);
estamos nos referimos endereo do bloco ocupado pela varivel num. A instruo significa: "leia
o buffer do teclado, transforme o valor lido em um valor inteiro (2 bytes) e o armazene no bloco
localizado no endereo da varivel num".
Exemplo: Para se atribuir a um ponteiro o endereo de uma varivel escreve-se:
int *p, val=5;

// declarao de ponteiro e varivel

p = &val;

// atribuio

Observe que o ponteiro p deve ser declarado anteriormente com o mesmo tipo da varivel para a
qual ele deve apontar.
O operador contedo (*) determina o contedo (valor) do dado armazenado no endereo de um
bloco apontado por um ponteiro. Por exemplo, *p determina contedo do bloco apontado pelo ponteiro p.
De forma resumida: o operador (*) determina o contedo de um endereo.
Exemplo: Para se atribuir a uma varivel o contedo de um endereo escreve-se:
int *p = 0x3f8, val;

// declarao de ponteiro e varivel

val = *p;

// atribuio

Observaes:
O operador endereo (&) somente pode ser usado em uma nica varivel. No pode ser usado

em expresses como, por exemplo, &(a+b).


O operador contedo (*) somente pode ser usado em variveis ponteiros.
91

Programa Exemplo: O arquivo e0802.cpp contm um programa que mostra como se


manipulam ponteiros e variveis. Como se transportam informaes entre ponteiros e variveis.

8.3 Operaes elementares com ponteiros


Ponteiros so variveis especiais e obedecem a regras especiais. Deste modo, existem uma srie de
operaes (aritmticas, lgicas, etc.) envolvendo ponteiros que so permitidas e outras no. A seguir so
destacadas algumas operaes que podem ser executadas com ponteiros.
A um ponteiro pode ser atribudo o endereo de uma varivel comum.
Exemplo: Observe o trecho abaixo:
...
int *p;
int s;
p = &s;

// p recebe o endereo de s

...
Um ponteiro pode receber o valor de outro ponteiro, isto , pode receber o endereo apontado
por outro ponteiro, desde que os ponteiros sejam de mesmo tipo.
Exemplo: Observe o trecho abaixo:
...
float *p1, *p2, val;
p1 = &val;

// p1 recebe o endereo de val...

p2 = p1;

// ...e p2 recebe o contedo de p2 (endereo de val)

Um ponteiro pode receber um endereo de memria diretamente. Um endereo um numero

inteiro. Em geral, na forma hexadecimal (0x....). Nesta atribuio devemos, em geral,


forar uma converso de tipo usando casting para o tipo de ponteiro declarado.

Exemplo: Observe o trecho abaixo:


...
int *p1;
float p2
p1 = 0x03F8;
92

// endereo da porta serial COM1

p2 = (float)0x0FF6; // casting
...
A um ponteiro pode ser atribudo o valor nulo usando a constante simblica NULL (declarada

na biblioteca stdlib.h). Um ponteiro com valor NULL no aponta para lugar nenhum!
Algumas funes a utilizam para registrar uma atribuio ilegal ou sem sucesso (ver funo
malloc() adiante)
Exemplo: Observe o trecho abaixo:
#include <stdlib.h>
...
char *p;
p = NULL;
...
Uma quantidade inteira pode ser adicionada ou subtrada de um ponteiro. A adio de um

inteiro n a um ponteiro p far com que ele aponte para o endereo do n-simo bloco seguinte.
Exemplo: Observe o trecho abaixo:
...
double *p, *q, var;
p = &var
q = ++p;

// q aponta para o bloco seguinte ao ocupado por var

p = q - 5;// p aponta para o quinto bloco anterior a q


...
Dois ponteiros podem ser comparados (usando-se operadores lgicos) desde que sejam de
mesmo tipo.
Exemplo: Observe o trecho abaixo:
...
if(px == py){

// se px aponta para o mesmo bloco que py ...

if(px > py){

// se px aponta para um bloco posterior a py ...

if(px != py){

// se px aponta para um bloco diferente de py ...

if(px == NULL) // se px nulo...


...
Programa Exemplo: O arquivo e0803.cpp contm um programa que mostra como se utilizam
algumas operaes elementares com ponteiros com ponteiros.

93

8.4 Ponteiros, endereos e funes


Porque usar ponteiros? A primeira vantagem da utilizao de ponteiros em programas talvez esteja
relacionada a sua utilizao como argumentos de funes.

8.4.1

Passagem de dados por valor ou por referencia

No captulo 6 (seo 6.5) afirma-se que o valor de uma varivel var de uma funo fun_1()
passada para uma outra funo fun_2() no podem ser alterado pela funo fun_2(). De fato, isto
verdade se passamos o valor da varivel var para a funo fun_2(). Mas o valor de var pode ser
alterado por fun_2() passamos seu endereo.
No primeiro caso, dizemos que a passagem de dados de uma funo para outra ocorreu por valor.
No segundo caso, dizemos que houve uma passagem por referncia. Vejamos em detalhe uma definio
destes tipos de passagem de dados entre funes:
Passagem por Valor: A passagem por valor significa que passamos de uma funo para outra o
valor de uma varivel, isto , a funo chamada recebe um cpia do valor da varivel. Assim qualquer
alterao deste valor, pela funo chamada, ser uma alterao de uma cpia do valor da varivel. O valor
original na funo chamadora no alterado pois o valor original e copia ficam em blocos de memria
diferentes.
Passagem por Referencia: A passagem por referencia significa que passamos de uma funo para
outra o endereo de uma varivel, isto , a funo chamada recebe sua localizao na memria atravs de
um ponteiro. Assim qualquer alterao no contedo apontado pelo do ponteiro ser uma alterao no
contedo da varivel original. O valor original alterado.
Sintaxe: A sintaxe da passagem de endereo a seguinte:
na funo chamada:
tipof nomef(tipop nomep){
...
onde:
tipof o tipo de retorno da funo.
nomef o nome da funo chamada.
tipop o tipo do ponteiro (igual ao tipo da varivel passada).
nomep o nome do ponteiro.
94

na funo chamadora:
...
nomef(end_var);
...
onde:
nomef o nome da funo chamada.
end_var o endereo da varivel passada como argumento.
Exemplo: Observe o exemplo abaixo:
void troca(int *p1, int *p1){ // declarao da funo
//

Observe: ponteiros

int temp;

// varivel temporria

temp = *p1;

// temp recebe o contedo apontado por p1

*p1 = *p2;

// o contedo de p1 recebe o contedo de p2

*p2 = temp;

// o contedo de p2 recebe o valor de temp

}
void main(){

// programa principal

int a,b;

// declarao das variveis

scanf("%d %d",&a,&b);

// leitura das variveis

troca(&a,&b);

// passagem dos endereos de a e b

printf("%d %d",a,b);

// imprime valores (trocados!)

}
Neste exemplo temos uma funo troca() que troca entre si os valores de duas variveis. Esta
funo recebe os endereos das variveis passadas pela funo main(), armazenando-os nos ponteiros
p1 e p2. Dentro da funo, troca-se os contedos dos endereos apontados.
Programa Exemplo: O arquivo e0804.cpp contm um programa que mostra a diferena entre
a passagem de dados por valor e passagem por referencia.

8.4.2

Retorno de dados em funes

A passagem por referencia permite que (formalmente) uma funo retorne quantos valores se
desejar. Dissemos no captulo 6 (seo 6.2) que uma funo somente pode retornar um valor. Isto continua

95

sendo valido pois o C assim define funes. Porem com o uso de ponteiros pode-se contornar esta situao.
Vejamos:
Imagine que queremos escrever uma funo stat() com a finalidade de calcular a media aritmtica e o
desvio padro de um conjunto de dados. Observe: o retorno destes dados no poder ser via instruo
return() pois isto no permitido. A soluo criar (na funo main(), por exemplo) duas variveis
para armazenar os valores desejados (med e desvio, por exemplo) e ento passar os endereos destas
variveis para a funo stat(). A funo recebe esses endereos e os armazena em ponteiros (pm e pd,
por exemplo). Em seguida, faz os clculos necessrios, armazenando-os nos endereos recebidos. Ao
trmino da execuo da funo os valores de med e desvio sero atualizados automaticamente. Este
recurso bastante utilizado por programadores profissionais.
Programa Exemplo: O arquivo e0805.cpp contm um programa que mostra o retorno de
vrios dados de uma funo. Na verdade trata-se da passagem de valores por referencia.

8.4.3

Ponteiro como argumento de funo

Observe que nos exemplos acima, a passagem de endereos foi feita atravs do operador endereo
(&). Tambm possvel passar um endereo atravs de um ponteiro j que o contedo de um ponteiro um
endereo.
Exemplo: Observe o trecho de programa abaixo.
...
float *p, x;
p = &x;
funo(p); // passagem do ponteiro com o endereo de x.
...
Programa Exemplo: O arquivo e0806.cpp contm um programa que mostra a passagem de
endereo para uma funo usando um ponteiro. Observe a sintaxe alternativa para a funo scanf()!

8.5 Ponteiros, vetores e strings


8.5.1

96

Ponteiros e vetores

No capitulo 7 foi mostrado que na passagem de vetores para funes especifica-se apenas o nome
do vetor e que modificaes nos elementos do vetor dentro da funo chamada alteram os valores do vetor
no programa chamador (seo 7.4). Isto se deve ao fato de que, na linguagem C, vetores so intimamente
relacionados a ponteiros.
Em C, o nome de um vetor tratado como o endereo de seu primeiro elemento. Assim ao se
passar o nome de um vetor para uma funo est se passando o endereo do primeiro elemento de um
conjunto de endereos de memria.
Por exemplo, se vet um vetor, ento vet e &vet[0] representam o mesmo endereo. E mais,
podemos acessar o endereo de qualquer elemento do vetor do seguinte modo: &vet[i] e equivalente a
(vet + i). Aqui deve-se ressaltar que (vet + i) no representa uma adio aritmtica normal mas
o endereo do i-simo elemento do vetor vet (endereo contado a partir do endereo inicial vet[0]).
Do mesmo modo que se pode acessar o endereo de cada elemento do vetor por ponteiros, tambm
se pode acessar o valor de cada elemento usando ponteiros. Assim vet[i] equivalente a *(vet +
i). Aqui se usa o operador contedo (*) aplicado ao endereo do i-simo elemento do vetor vet.
Programa Exemplo: O arquivo e0807.cpp contm um programa que mostra a equivalncia
entre ponteiros e vetores.

8.5.2

Ponteiros e strings

Como dissemos, vagamente na seo 2.3.4, uma string um conjunto ordenado de caracteres.
Podemos, agora, dizer muito mais: Em C, uma string um vetor unidimensional de elementos caracteres
ASCII, sendo o ultimo destes elementos o caracter especial \0.
Sintaxe: As duas maneiras mais comuns de declararmos uma string so:
char nome[tam];
ou
char *nome;
onde:
nome o nome do vetor de caracteres e
tam seu tamanho.

97

Observe que sendo um vetor, uma string pode ser declarada tambm como um ponteiro. Alias a
segunda declarao representa justamente isto. Sabendo isto podemos realizar uma grande variedade de
manipulaes com strings e caracteres. Existe uma biblioteca padro C chamada string.h que
providencia algumas funes de manipulao de strings muito teis.
Programa Exemplo: O arquivo e0808.cpp contm um programa que mostra algumas
operaes usando-se strings (vetores e ponteiros).

8.6 Alocao Dinmica de Memria


Os elementos de um vetor so armazenados seqencialmente na memria do computador. Na
declarao de um vetor, (por exemplo: int vet[10]) dito ao processador reservar (alocar) um certo
numero de blocos de memria para armazenamento dos elementos do vetor. Porem, neste modo de
declarao, no se pode alocar um numero varivel de elementos (veja seo 7.3.2).
A linguagem C permite alocar dinamicamente (em tempo de execuo), blocos de memria usando
ponteiros. Dada a intima relao entre ponteiros e vetores, isto significa que podemos declarar
dinamicamente vetores de tamanho varivel. Isto desejvel caso queiramos poupar memria, isto no
reservar mais memria que o necessrio para o armazenamento de dados.
Para a alocao de memria usamos a funo malloc()(memory allocation) da biblioteca
alloc.h. A funo malloc() reserva, seqencialmente, um certo numero de blocos de memria e
retorna, para um ponteiro, o endereo do primeiro bloco reservado.
Sintaxe: A sintaxe geral usada para a alocao dinmica a seguinte:
pont = (tipo *)malloc(tam);

onde:
pont o nome do ponteiro que recebe o endereo do espao de memria alocado.
tipo o tipo do endereo apontado (tipo do ponteiro).
tam o tamanho do espao alocado: numero de bytes.
A sintaxe seguinte, porem, mais clara:
pont = (tipo*)malloc(num*sizeof(tipo));

98

onde:
num o numero de elementos que queremos poder armazenar no espao alocado.
Exemplo: Se queremos declarar um vetor chamado vet, tipo int, com num elementos podemos
usar o trecho abaixo:
...
int *vet;

// declarao do ponteiro

vet = (int*)malloc(num*2); // alocao de num blocos de 2 bytes


...
ou ainda
...
int *vet; // declarao do ponteiro
vet = (int*) malloc(num * sizeof(int));
...
Caso no seja possvel alocar o espao requisitado a funo malloc() retorna a constante
simblica NULL.
Sintaxe: Para liberar (desalocar) o espao de memria se usa a funo free(), cuja sintaxe a
seguinte:
free(pont);
onde:
pont o nome do ponteiro que contem o endereo do inicio do espao de memria reservado.
Programa Exemplo: O arquivo e0809.cpp contm um programa que mostra como se utiliza a
Alocao Dinmica de Memria.

8.7 Ponteiros para Funes


At agora usamos ponteiros para apontar para endereos de memria onde se encontravam as
variveis (dados). Algumas vezes necessrio apontar para funes, isto , apontar para o endereo de
memria que contem o inicio das instrues de uma funo. Quando assim procedemos, dizemos que
usaremos ponteiros para funes.

99

8.7.1 Ponteiros como chamada de funo.


Um uso de ponteiros para funes passar uma funo como argumento de outra funo. Mas
tambm se pode usar ponteiros para funes ao invs de funes nas chamadas normais de funes.
Sintaxe: A sintaxe de declarao de ponteiro para funes a seguinte:
tipo_r (*nome_p)(lista);
onde:
tipo_r o tipo de retorno da funo apontada.
nome_p o nome do ponteiro que apontara para a funo.
lista a lista de argumentos da funo.
Exemplo: Suponha que temos uma funo declarada como:
float fun(int a, int b){
...
}
o ponteiro correspondente ser:
float (*pt)(int,int);
Observe que o ponteiro para funo deve ser declarado entre parnteses. Observe tambm que o
ponteiro e a funo retornam o mesmo tipo de dado e que tem os mesmos argumentos.
Sintaxe: Para atribuirmos o endereo de uma funo para um ponteiro usamos a seguinte sintaxe:
pont = &funo;

onde:
pont o nome do ponteiro.
funo o nome da funo.
Se um ponteiro contem o endereo de uma funo, ele pode ser usado no lugar da chamada da
funo.
Exemplo: o trecho de programa abaixo usa um ponteiro para chamar uma funo:
float fun(int a,int b){
...
}
100

void main(void){
float temp;
float (*pt)(int,int);
pt = &fun;
temp = (*pt)(10,20); // eqivale a: temp = fun(10,20);
...
}
Programa Exemplo: O arquivo e0810.cpp contm um programa que mostra como se utiliza o
ponteiro para funo.

8.7.2

Passando uma funo como argumento de outra funo.

Outra utilizao de ponteiros para funes na passagem de uma funo como argumento para
outra funo. Para que isso ocorra necessitamos:
Na declarao da funo a ser passada:

i) Nada de especial, apenas a definio normal:


tipo nome_p(lista){
...
}
Exemplo:
float soma(float a,float b){
...
}
Na funo receptora:

i) Declarar o ponteiro que recebe a funo passada na lista de argumentos:


tipo nome_r(..., tipo (*pt)(lista), ...){
Exemplo:
float grad(float x, float y, float (*p)(float,float)){
ii) Usar o ponteiro para funes nas chamadas da funo passada:
var = (*pt)(lista);
Exemplo:
101

valor = (*p)(x,y);
Na funo principal:
i) Passar o nome da funo chamada para a funo receptora:
var = nome_g(... , nome_p , ...);
Exemplo:
g = grad(x,y,soma);
Programa Exemplo: O arquivo e0811.cpp contm um programa que mostra como se utiliza
ponteiros na passagem de funes

102

Você também pode gostar