O documento descreve a arquitetura de Von Neumann utilizada nos computadores pessoais, onde a mesma memória é usada para armazenar tanto dados quanto código executável. Também explica o conceito de ponteiros em C, que são variáveis que armazenam endereços de memória e permitem a manipulação direta desses endereços no código.
2. 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.
5. 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
0x0
0x1
0x2
0x3
0x4
0x5
0x6
0x7
E se ela tivesse 1GB, iria do
Endereço 0 a qual número
decimal?
E hexadecimal?
6. 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
0x0
0x1
0x2
0x3
0x4
0x5
0x6
0x7
E se ela tivesse 1GB, iria do
Endereço 0 a qual número
decimal?
Resp.: 1073741823
E em hexadecimal?
Resp.: 3FFFFFFF
9. 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 envolve
o uso de um endereço
e um valor.
Esta instrução envolve
apenas endereços.
10. 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.
11. 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.
13. Ponteiro
● Nos exemplos acima, os endereços
possuíam 1 B de tamanho.
● Quantos valores podem ser armazenados
com 1 B?
14. 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?
15. 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!
16. 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
22. 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
23. 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.
25. 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.
26. 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>;
30. 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>
34. 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
35. Atribuições e tipos
● Exemplo 2:
a = 0 - b;
● Exemplo 3:
float f = 3.14 * 4.0 / a ;
int int
float float
36. 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>.
37. 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.
38. 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*
39. Atribuições e tipos
● Exemplo 2:
float f = 3.14;
float *p;
p = &f;
● Exemplo 3:
int b = 13;
char * pi;
pi = &b;
float* = & float
float* = float*
char* = & int
char* = int* ERRADO!
40. 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.
41. 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”.
42. 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?
43. 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?
44. 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?
45. Atribuições e tipos
● Exemplo 4:
long l1 = 2000, l2 = 0;
long *p1, *p2;
p1 = &l1;
p2 = &l2;
*p2 = 2 * *p1;
printf(“%in”,l2);
Qual o valor aparecerá na tela?
46. 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.
47. 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.
48. 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*.
49. 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.
55. 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;
56. 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;
57. 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;
58. 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.
60. 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.
63. 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?
64. Exercícios
2) Por que o tamanho dos ponteiros da
questão 1 são iguais apesar de serem
ponteiros para tipos diferentes?