Você está na página 1de 65

Ponteiros

Yuri Tavares dos Passos


Arquitetura de Von
Neumann
● Os computadores pessoais utilizam uma
arquitetura que é baseada na arquitetura de
Von Neumann.
● Esta arquitetura se baseia na utilização de um
mesmo bloco de memória para armazenar
dados e código executável.
● O processador fica responsável por buscar
código na mesma memória em que busca por
um dado.
Arquitetura de Von
Neumann
Busca e
executa

Lê e escreve
Arquitetura de Von
Neumann
Dados Busca e
executa
Código

Dados

Código

Código

Dados

Lê e escreve
Memória Principal
Arquitetura de Von
Neumann
● Suponha uma memória RAM com
capacidade de 8 B. Ela pode ser
endereçada dos endereços 0 a 7, cada um
com a capacidade de 1 B.
0x0
0x1 E se ela tivesse 1GB, iria do
0x2 Endereço 0 a qual número
0x3 decimal?
0x4
0x5
0x6 E hexadecimal?
0x7

1B
Arquitetura de Von
Neumann
● Suponha uma memória RAM com
capacidade de 8 B. Ela pode ser
endereçada dos endereços 0 a 7, cada um
com a capacidade de 1 B.
0x0
0x1 E se ela tivesse 1GB, iria do
0x2 Endereço 0 a qual número
0x3 decimal?
0x4
0x5 Resp.: 1073741823
0x6 E em hexadecimal?
0x7 Resp.: 3FFFFFFF
1B
Arquitetura de Von
Neumann
0x19 16

0x20 MOVE 16 0x19

0x21 3.89

0x22 SOME 0x21 0x19

0x23 MOVE 0x19 0x24

0x24 ?

Endereços Memória Principal


Arquitetura de Von
Neumann
0x19 00010000

0x20 01010101

0x21 00100111

0x22 10100010

0x23 01010101

0x24 00010000

Endereços Memória Principal


Arquitetura de Von
Neumann
0x19 16
Esta instrução envolve
0x20 MOVE 16 0x19 o uso de um endereço
e um valor.
0x21 3.89

Esta instrução envolve


0x22 SOME 0x21 0x19
apenas endereços.
0x23 MOVE 0x19 0x24

0x24 ?

Endereços Memória Principal


Ponteiro
● Como manipular endereços de memória no
código-fonte?
– Utilizando um tipo de variável para endereços.
● Um ponteiro ou apontador é uma variável
que guarda endereços de memória.
● Endereços de memória são valores inteiros
que podem ser armazenados e utilizados
no código.
Ponteiros
● Aplicações de ponteiros:
– Estruturas de dados mais eficientes.
– Aplicações que envolvam a organização dos dados.
– Aplicações que devem ser rápidas.
– Alocação dinâmica de memória.
– Acessar variáveis que não são visíveis em uma função.
– Retornar mais de um valor para uma função.
● A tentativa de evitar o uso de ponteiros implicará quase
sempre códigos maiores e de execução mais lenta.
Ponteiro
0x19 16

0x20 3.98

0x21 0x20 São variáveis


que apontam
0x22 SOME 0x24 0x19 para outras
0x23 MOVE 0x19 0x24

0x24 0x22
Ponteiro
● Nos exemplos acima, os endereços
possuíam 1 B de tamanho.
● Quantos valores podem ser armazenados
com 1 B?
Ponteiro
● Nos exemplos acima, os endereços
possuíam 1 B de tamanho.
● Quantos valores podem ser armazenados
com 1 B?
– Resp.: 1 B = 8 b
28 = 256
● Quantos Bytes as memórias vendidas no
comércio possuem?
Ponteiro
● Suponha um computador de 1 GB.
● Ela pode possuir 1073741824 endereços de
1 B distintos. Para representar o maior
endereço (3FFFFFFF), precisamos de 4 B.
● Portanto, para armarzenarmos um endereço
numa máquina atual, precisaremos de 4 B ou
mais!
Ponteiro
● Outro detalhe do exemplo de máquina
apresentado é o tamanho dos nossos
dados. Quantos bytes eles usam?
0x19 16

0x20 MOVE 16 0x19

0x21 3.89

0x22 SOME 0x21 0x19

0x23 MOVE 0x19 0x24

0x24 0
Ponteiro
● Qual tipo de dado em C possui 1 B?
Ponteiro
● Qual tipo de dado em C possui 1 B?
● Resp.: char e unsigned char
Ponteiro
● Os tipos básicos de C possuem todos 1 B?
Tipos de dados básicos

Tipos básicos Tamanho Precisão

char -> p/ caracteres 1 byte - 128 a + 127

int -> p/ inteiros 4 bytes* -2147483648 a


2147483647
float -> p/ reais 4 bytes* -3.4E-38 a +3.4E+38
double -> p/ reais 8 bytes* -1.7E-308 a +1.7E+308

* Variam a depender do processador


Ponteiro
● Considere o seguinte código:
Ponteiro
● Vamos pegar um número inteiro, como 0.
Em uma célula de 4 B (= 32 b), temos
como representação binária 32 zeros
consecutivos.
● Um número inteiro como 1465232370
temos como representação binária
01010111010101011010101111110010
– Em hexadecimal: 5755ABF2
Ponteiro
● Um caractere como 'a', na tabela de
conversão ASCII é igual ao número 65.
Como binário temos 01000001.
– Em hexadecimal: 41
● O bloco de memória do código
apresentado seria como apresentado a
seguir.
Ponteiro

}
0xbff43513 00000000
0xbff43514 00000000 a=0
0xbff43515 00000000
0xbff43516 00000000

}
0xbff43517 01010111
0xbff43518 01010101
b = 1465232370
0xbff43519 10101011
0xbff4351a 11110010
0xbff4351b 01000001 } c = 'a'
Ponteiro
● O endereço do inteiro a é 0xbff43513. Mas
o inteiro vai de 0xbff43513 a 0xbff43516
● O endereço do caractere c é 0xbff4351b.
Apenas este endereço é suficiente para
guardá-lo.
● Além do endereço, o programa precisa
saber o tamanho do dado que aquele
endereço armazena.
Ponteiro
● Assim, um ponteiro além de armazenar o
endereço de uma variável também possui o
tamanho do dado daquela variável.
● Para se declarar uma variável do tipo
ponteiro utiliza-se o símbolo * após o tipo de
dado que este ponteiro deve apontar.
● A sintaxe de declaração é:
<tipo de dado> * <nome do ponteiro>;
Ponteiro
Ponteiro
● Para obter o endereço de memória de uma
variável utiliza-se o operador &.
● A sintaxe deste operador é:
& <variável>
Ponteiro
Ponteiro
● Para acessar o conteúdo do que um ponteiro
está apontando, usa-se *.
● A operação que realiza o acesso a algum dado
apontado por um ponteiro é conhecida como
desreferenciamento.
– Um endereço de memória é uma referência a um
dado.
● Dentro de expressões, a sintaxe é:
* <variável ponteiro>
Ponteiro
Ponteiro
● Se fosse para escrever na tela os
endereços onde as variáveis ponteiros pa,
pb e pc estão o que deveria ser feito?
Ponteiro
Atribuições e tipos
● Em um compilador, o tipo de dado do lado
esquerdo de uma atribuição deve ser igual
ao tipo do resultado do lado direito.
● Exemplo 1:
c = 'b';

char char
Atribuições e tipos
● Exemplo 2:
a = 0 - b;

int int
● Exemplo 3:
float f = 3.14 * 4.0 / a ;

float float
Atribuições e tipos
● Quando utilizamos o operador &, o
resultado é um endereço para a variável
aplicada a este operador.
● Se temos:
&<variável>;
● O operador & retorna um tipo igual a
“ponteiro do tipo da” <variável>.
Atribuições e tipos
● Para declaramos uma variável p1 ponteiro
para o tipo caractere fazemos:
char * p1;
● Para declaramos uma variável p2 ponteiro
para o tipo inteiro fazemos:
int * p2;
● Assim, podemos usar o * para denotar o tipo
ponteiro de algum tipo.
Atribuições e tipos
● A regra de igualdade entre tipos nos dois
lados de uma atribuição também funciona
para ponteiros.
● Exemplo 1:
char c = 'a';
char *p;
p = &c; char* = & char

Sabendo-se que & <tipo> = <tipo>*, temos:

char* = char*
Atribuições e tipos
● Exemplo 2:
float f = 3.14;
float *p;
p = &f; float* = & float
float* = float*
● Exemplo 3:
int b = 13;
char * pi;
pi = &b; char* = & int
char* = int* ERRADO!
Atribuições e tipos
● Quando utilizamos o operador *, o resultado
é um conteúdo para o mesmo tipo que o
ponteiro deve apontar.
● Se temos:
* <variável ponteiro>;
● O operador * retorna um tipo igual ao tipo
do ponteiro, sem um asterisco. Ou seja, ele
remove o termo “ponteiro” para aquele tipo.
Atribuições e tipos
● Exemplo 1:
O * em preto
– char c = 'b'; representa o
char d; operador
desreferenciamento.
char *p = &c;
d = *p; O * em vermelho
representa o tipo
“ponteiro de”.
char = * char*
Sabendo-se que * <ponteiro para tipo> = <tipo>, temos:
char = char
Atribuições e tipos
● Exemplo 2:
int x = 9, y;
int *p = &x;
y = 2 + *p;
int = int + * int*
int = int + int
int = int

Qual o valor de y?
Atribuições e tipos
● Exemplo 3:
long l1 = 2E12;
long *p1 = &l1;
l1 = 2 * *p1;
long = long * * long*
long = long * long
long = long

Qual o valor de l1?


Atribuições e tipos
● Exemplo 4:
long l1 = 2000, l2 = 0;
long *p1, *p2;
p1 = &l1;
p2 = &l2;
*p2 = 2 * *p1;

Existe algo de errado na última expressão?


Atribuições e tipos
● Exemplo 4:
long l1 = 2000, l2 = 0;
long *p1, *p2;
p1 = &l1;
p2 = &l2;
*p2 = 2 * *p1;
printf(“%i\n”,l2);

Qual o valor aparecerá na tela?


Observação sobre * e &
● Observação
– Os operadores & e * não podem ser apliados
a constantes.
● Exemplo:
int * p = &12;
– Isto não é possível pois a constante 12 não
está armazenada em nenhum endereço.
Observação sobre * e &
● Exemplo:
char c = * 0x5623;
– Interpretando este exemplo em linguagem
humana estaríamos dizendo que o valor de c
será igual ao valor do byte que estiver no
endereço 0x5623.
– Mas o S.O. protege este endereço, pois ele
pode estar sendo utilizado por outro programa,
evitando falha de segurança.
Ponteiro sem tipo
● Existe também um tipo de ponteiro que não está
associado a nenhum tipo de variável: void *.
● Este tipo é utilizado quando se deseja acessar um
endereço de memória ignorando o tipo de dado que
existe neste endereço.
● Exemplo de declaração:
void * p;
void * bloco_de_memória;
● Contudo, atribuições para este tipo de variável
necessitam de conversões explícitas para void*.
Conversão entre tipos
● Existem dois tipos de conversões
– Implícitas
– Explícitas
● As conversões ímplicitas são feitas
automaticamente pelo compilador, quando
o tipo convertido é um subconjunto do tipo
a ser atribuído.
Conversão implícita
● Exemplo:
int i = 1;
float f = i;

Isto está errado?


Conversão implícita
● Exemplo:
int i = 1;
float f = i;
printf(“%f\n”,f);

Que valor será escrito na tela?


Conversão implícita
● Exemplo:
int i = 1;
float f = i; ℝ
float int

Conversão implícita
● Exemplo:
int i = 1;
float f = (float) i;
● Foi realizada uma conversão implícita de
inteiro para real.
Conversão implícita

double

float

int

char
Conversão implícita
● O compilador gcc não acusa erro se for
esquecida a conversão de um tipo mais
abrangente para um tipo menos
abrangente. Mas você será avisado caso
esteja fazendo.
● Exemplo:
float f = 1.2;
int i = f;
Conversão explícita
● A conversão explícita ocorre quando se
deseja forçar a conversão entre tipos.
● Utiliza-se o nome do tipo a ser convertido
entre parentêses.
● Exemplo:
float f = 1.2;
int i = (int) f;
Conversão explícita
● É útil quando se sabe que tipo de dado está armazenado
em uma certa variável, evitando os avisos dos
compiladores.
● Deve ser usado com extrema sabedoria, pois dados
podem ser perdidos!
– 0.0000122323 ≠ 0.
● Para se utilizar o ponteiro sem tipo, deve ser utilizada esta
conversão.
– Exemplo:
char c = 'a';
void* p = (void*) &c;
Incremento e decremento
● Ao se utilizar adição e subtração com
ponteiros o comportamento destas
operações serão diferentes do
apresentado anteriormente.
● A soma e a subtração são realizadas em
unidades que correspondem ao tamanho
em bytes do tipo de dado que aquele
ponteiro faz referência.
Incremento e decremento
● Exemplo 1:
Incremento e decremento
● No Exemplo 1, o valor do ponteiro pchar é
aumentado de 1 como é feito normalmente
com o ++.
● Veja que o valor de pchar foi aumentado
de apenas mais uma unidade.
Incremento e decremento
● Exemplo 2:
Incremento e decremento
● Exemplo 3:
Exercícios
1) Escreva um programa com sizeof e
verifique o tamanho dos seguintes tipos de
dados: unsigned int *, int *, long*, float*,
void*. Responda: qual o tamanho de cada
um?
Exercícios
2) Por que o tamanho dos ponteiros da
questão 1 são iguais apesar de serem
ponteiros para tipos diferentes?
Exercícios
● Lista!

Você também pode gostar