Esta é uma pré-visualização de arquivo. Entre para ver o arquivo original
1
LABORATÓRIO DE PROGRAMAÇÃO I
Profa. Gisele Busichia Baioco
gisele@ft.unicamp.br
Algoritmos Estruturados e
Linguagem de Programação Estruturada
Ponteiros em C
Parte I – Conceitos Básicos
1 Introdução
Uma das mais poderosas características oferecidas pela linguagem C é o uso de
ponteiros. Ponteiros proporcionam o acesso a variáveis sem referenciá-las diretamente, ou
seja, por meio do endereço de memória das variáveis.
Algumas razões para o uso de ponteiros:
• fornecem maneiras pelas quais as funções em C podem modificar os argumentos que
recebem;
• usá-los no lugar de vetores, matrizes e strings para aumentar a efeciência;
• para alocação dinâmica de memória;
• para criar estruturas de dados complexas, como listas encadeadas, pilhas, árvores, etc.
Além dos ponteiros serem uma das características mais fortes de C, também é a mais
perigosa. O uso incorreto de ponteiros provoca desde erros difíceis de serem encontrados
até uma queda no sistema.
2 O que são Ponteiros?
Uma variável do tipo int guarda números inteiros. As variáveis do tipo float guardam
números de ponto flutuante. Uma variável do tipo char guarda caracteres. Um ponteiro é
uma variável que guarda endereços de memória. Ou seja, uma variável do tipo ponteiro
contém o endereço de memória de uma outra variável e pode-se dizer que a primeira variável
aponta para a segunda.
Esquematicamente tem-se:
Memória do Computador
Endereço Conteúdo
1000 1003
1001
1002
1003
1004
1005
1006
... ...
2
Assim, usado um ponteiro é possível acessar o conteúdo de memória de uma variável
sem referenciá-la diretamente, o que é conhecido como endereçamento indireto.
Além do endereçamento indireto, um ponteiro em C também pode ser utilizado em um
programa para alocar memória em tempo de execução, o que é conhecido como alocação
dinâmica de memória.
Essas duas possibilidades de utilização de ponteiros serão abordadas com detalhes no
decorrer deste texto.
3 Declarando e Utilizando Variáveis Ponteiro
Assim como todas as variáveis, uma variável ponteiro também tem tipo. Em C,
quando se declara uma variável ponteiro, deve-se informar para que tipo de variável irá
apontar. Por exemplo, um ponteiro int aponta para uma variável do tipo inteiro, isto é, guarda
o endereço de um inteiro.
Sintaxe de declaração de variáveis ponteiro em C:
tipo-de-dado *nome;
onde:
tipo-de-dado: é um tipo de dado válido em C;
nome: é um identificador válido para a variável ponteiro. É o asterisco (*) que faz o
compilador saber que a variável não vai armazenar um valor, mas sim um endereço de
memória que contenha um valor do tipo especificado.
Exemplos de declarações:
int *p; /* declara um ponteiro para o tipo inteiro */
char *temp, *p2; /* declara dois ponteiros para o tipo caractere */
Os ponteiros p, temp e p2 ainda não foram inicializados (como toda variável do C que
é apenas declarada). Isso significa que eles apontam para um lugar indefinido da memória.
Esse lugar pode estar, por exemplo, na porção da memória reservada ao sistema operacional
do computador. Usar o ponteiro nessas circunstâncias pode levar a um travamento do
computador. Desse modo, um ponteiro deve ser inicializado (apontado para algum lugar
conhecido) antes de ser usado.
Para atribuir um valor a um ponteiro recém-criado a fim de inicializá-lo, bastaria
igualá-lo a um valor de memória. Mas, como saber a posição na memória de uma variável do
programa? Seria muito difícil saber o endereço de cada variável usada em um programa,
mesmo porque esses endereços são determinados pelo compilador (em tempo de compilação)
e realocados na execução. Pode-se então deixar que o compilador faça esse trabalho. Para
saber o endereço de uma variável basta usar o operador &. Por exemplo:
int cont;
int *p;
cont = 10;
p = &cont; /* obtém o endereço de cont e atribui para p */
Nesse exemplo declarou-se uma variável do tipo inteiro cont e uma variável ponteiro
para inteiro p. Em seguida atribuiu-se o valor 10 para cont. A expressão &cont fornece o
endereço da variável cont, o qual é armazenado em p. Nesse momento, p está inicializado e
pode, então, ser utilizado.
3
Deve-se observar no exemplo anterior que o valor de cont não foi alterado, ou seja,
cont continua com o conteúdo 10. Pode-se, então, alterar o valor de cont usando p. Para tanto
usa-se o operador *.
Considerando o exemplo anterior, no momento em que se executa a instrução:
p = &cont;
A expressão *p passa a ser equivalente à própria variável cont. Isso significa que,
pode-se alterar o valor de cont para 12, basta fazer:
*p = 12;
Observação: O símbolo utilizado para multiplicação e o símbolo que obtém o valor
armazenado no endereço referenciado por ponteiros são iguais (*). Apesar disso, esses
operadores não tem nenhuma relação entre si. Os operadores de ponteiros & e * têm
precedência mais alta que os operadores aritméticos, exceto o menos unário, com o qual têm a
mesma precedência.
Exemplos do uso de variáveis ponteiro:
#include <stdio.h>
main()
{
int num, valor;
int *p;
num = 55;
p = # /* Obtém o endereço de num */
valor = *p; /* a variável valor recebe o mesmo valor de num de
maneira indireta */
printf("Valor = %d\n", valor);
printf("Endereco para onde o ponteiro aponta: %p\n", p);
printf("Valor da variavel apontada: %d\n", *p);
}
Deve-se observar que o código %p usado na função printf() indica que deve imprimir
um endereço (em hexadecimal).
#include <stdio.h>
main()
{
int num,*p;
num = 55;
p = # /* Obtém o endereço de num */
printf("Valor inicial: %d\n", num);
*p = 100; /* Altera o valor de num de maneira indireta */
printf("Valor final: %d\n", num);
}
4 Expressões com Ponteiros
Em geral, expressões que envolvem ponteiros obedecem as mesmas regras das
expressões em C.
4
4.1 Atribuições com Ponteiros
Considerando dois ponteiros p1 e p2 pode-se atribuir o conteúdo de p1 a p2 fazendo
p1 = p2. A atribuição p1 = p2 faz com que p1 aponte para o mesmo endereço que p2. Deve-
se observar que se fosse necessário fazer com que a variável apontada por p1 tenha o mesmo
conteúdo da variável apontada por p2 o comando seria *p1=*p2.
Por exemplo, o programa seguinte exibe o endereço de memória de x (em
hexadecimal) por meio do ponteiro p2:
#include <stdio.h>
main()
{
int x;
int *p1, *p2;
p1 = &x;
p2 = p1; /* atribui p1 para p2 */
printf("%p %p %p",&x, p1, p2); /* imprime 3 vezes o endereço
de memória de x */
}
4.2 Aritmética com Ponteiros
Em C, pode-se fazer apenas duas operações aritméticas em ponteiros: incremento (+) e
decremento (-).
Cada vez que o computador incrementa (ou decrementa) um ponteiro, ele aponta para
o endereço de memória do próximo elemento (ou elemento anterior) de seu tipo. Por exemplo,
quando o computador incrementar um ponteiro do tipo char, seu endereço será aumentado
em 1 (o tipo char ocupa 1 byte na memória); porém, quando o computador incrementar um
ponteiro do tipo int, seu endereço será aumentado em 2 (o tipo int ocupa 2 bytes de
memória).
Esquematicamente tem-se:
char *c = 3000;
Memória do Computador
Endereço Conteúdo
c 3000
c + 1 3001
c + 2 3002
c + 3 3003
c + 4 3004
c + 5 3005
... ...
int *i = 3000;
Memória do Computador
Endereço Conteúdo
i 3000 3001
i + 1 3002 3003
i + 2 3004 3005
... ...
5
Supondo
que p é um ponteiro, as operações são escritas como:
p++; /* ou p = p + 1 */
p--; /* ou p = p – 1 */
p = p + 5; /* ou p += 5 */
Mais uma vez vale lembrar que as operações são com os ponteiros e não com o
conteúdo das variáveis para as quais eles apontam. Por exemplo, para incrementar o conteúdo
da variável apontada pelo ponteiro p, faz-se:
(*p)++; /* ou *p += 1 ou *p = *p + 1 */
Caso seja necessário usar o conteúdo do ponteiro 15 posições adiante, faz-se:
*(p+15)
Exemplos de operações válidas com ponteiros em C:
1) se p aponta para a variável x do tipo inteiro, então *p pode ocorrer em qualquer
contexto em que x possa ocorrer, por exemplo:
y = *p + 1; /* atribui a y o valor de x e soma um */
d = sqrt((double)*p); /* atribui a d a raiz quadrada de x,
o qual é convertido em um double
antes de ser usado por sqrt */
2) referências a ponteiros podem ocorrer do lado esquerdo de atribuições. Por
exemplo, tendo um ponteiro p que aponta a variável x do tipo inteiro, então:
p = &x; /* p aponta para x */
*p = 0; /* atribui 0 a x */
(*p)++ /* incrementa o valor de x */
Entretanto, existem operações que não podem ser realizadas com ponteiros. Por
exemplo, não se pode dividir ou multiplicar ponteiros, adicionar dois ponteiros, adicionar ou
subtrair valores do tipo float e double de ponteiros.
4.3 Comparações entre ponteiros
Pode-se comparar dois ponteiros usando operadores relacionais (<, <=, >, >=, ==, !=).
Mas que informação é obtida quando compara-se dois ponteiros? Bem, em primeiro
lugar, pode-se saber se dois ponteiros são iguais ou diferentes (== e !=), ou seja, se apontam
ou não para o mesmo endereço de memória. No caso de operações do tipo <, <=, >, >=
compara-se qual ponteiro aponta para uma posição mais alta (ou mais baixa) na memória.
A comparação entre dois ponteiros se escreve como a comparação entre outras duas
variáveis quaisquer, por exemplo:
p1 > p2