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
Código
Código
Dados
Código
Dados
Memória Principal
Busca e executa
Lê e escreve
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.
1 B
0x00x10x20x30x40x50x60x7
E se ela tivesse 1GB, iria doEndereço 0 a qual número decimal?
E hexadecimal?
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.
1 B
0x00x10x20x30x40x50x60x7
E se ela tivesse 1GB, iria doEndereço 0 a qual número decimal?Resp.: 1073741823E em hexadecimal?Resp.: 3FFFFFFF
Arquitetura de Von Neumann
16
MOVE 16 0x19
SOME 0x21 0x19
3.89
MOVE 0x19 0x24
?
Memória PrincipalEndereços
0x19
0x21
0x20
0x22
0x23
0x24
Arquitetura de Von Neumann
00010000
01010101
10100010
00100111
01010101
00010000
Memória PrincipalEndereços
0x19
0x21
0x20
0x22
0x23
0x24
Arquitetura de Von Neumann
16
MOVE 16 0x19
SOME 0x21 0x19
3.89
MOVE 0x19 0x24
?
Memória PrincipalEndereços
0x19
0x21
0x20
0x22
0x23
0x24
Esta instrução envolveo uso de um endereço e um valor.
Esta instrução envolveapenas endereços.
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
16
3.98
SOME 0x24 0x19
0x20
MOVE 0x19 0x24
0x22
0x19
0x21
0x20
0x22
0x23
0x24
São variáveis que apontam para outras
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?
16
MOVE 16 0x19
SOME 0x21 0x19
3.89
MOVE 0x19 0x24
0
0x19
0x21
0x20
0x22
0x23
0x24
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 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+38double -> p/ reais 8 bytes* -1.7E-308 a +1.7E+308
* Variam a depender do processador
Tipos de dados básicos
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
0xbff43514
0xbff43515
0xbff43516
0xbff43517
0xbff43518
0xbff43519
0xbff4351a
0xbff4351b
00000000
00000000
00000000
00000000
01010111
01010101
10101011
11110010
01000001
} a = 0
} b = 1465232370
} 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;
● Exemplo 3:
float f = 3.14 * 4.0 / a ;
int int
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;
● Exemplo 3:
int b = 13;char * pi;pi = &b;
float* = & floatfloat* = float*
char* = & intchar* = 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:– char c = 'b';
char d;char *p = &c;d = *p;
char = * char*Sabendo-se que * <ponteiro para tipo> = <tipo>, temos:char = char
O * em preto representa o operador desreferenciamento.
O * em vermelho representa o tipo “ponteiro de”.
Atribuições e tipos
● Exemplo 2:
int x = 9, y;int *p = &x;y = 2 + *p;
int = int + * int*int = int + intint = 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 * longlong = 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
char
int
float
double
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!