- Mapa de memória de um processo - Ponteiros
MCTA028 – Programação Estruturada
Luiz Rozante 3Q-2018
Material preparado a partir de slides dos profs. Jesús Mena-Chalco e Fabrício Olivetti
Alocação de memória
Na execução, um programa é um processo.
(processos de SOs são módulos executáveis únicos) Um processo ocupa parte da memória principal, reservada para:
instruções, dados,
pilha, heap
O espaço de endereços de um processo em execução é
dividido nessas 4 áreas (as mais importantes)
Alocação de memória
Modelo geral de organização da memória:
Alocação de memória
Text area: contém o código do programa e suas constantes.
Esta área é alocada durante a chamada exec e permanece do mesmo tamanho durante toda a vida do processo.
Data area: é a memória de trabalho do processo, onde ele
armazena suas variáveis globais e estáticas. Tem tamanho fixo ao longo da execução do processo.
Stack area: contém a pilha de execução, onde são
armazenadas os parâmetros, endereços de retorno e variáveis locais de funções. Pode variar de tamanho durante a execução do processo.
Heap area: contém áreas de memória alocadas a pedido do processo, durante sua execução. Varia de tamanho durante a vida do processo.
Alocação de memória
Um processo C suporta três tipos de alocação de memória::
A alocação estática ocorre quando são declaradas variáveis globais ou estáticas; geralmente usa a área Data.
A alocação automática ocorre quando são declaradas variáveis locais e parâmetros de funções. O espaço para a alocação
dessas variáveis é reservado quando a função é invocada, e liberado quando a função termina. Geralmente é usada a pilha (stack).
A alocação dinâmica, quando o processo requisita
explicitamente um bloco de memória para armazenar dados; o controle das áreas alocadas dinamicamente é manual ou
semi-automático: o programador é responsável por liberar as
áreas alocadas dinamicamente. A alocação dinâmica geralmente usa a área de heap.
Alocação estática
Ocorre com variáveis globais (alocadas fora de funções) .
Ocorre quando variáveis locais (internas a uma função) são alocadas usando o modificador static.
Uma variável alocada estaticamente mantém seu valor durante toda a vida do programa, exceto quando
explicitamente modificada.
Alocação automática
As variáveis definidas dentro de uma função (variáveis locais e parâmetros) são alocadas de forma automática na pilha de execução do programa (stack) a cada
chamada da função.
São descartadas quando a função encerra.
Pilha de execução geralmente é uma área pequena.
(problema para variáveis locais muito grande e funções
recurcivas)
Alocação dinâmica
O programa (processo) solicita explicitamente áreas de memória ao SO.
Depois de utilizá-las as libera quando não são mais necessárias.
- depois de liberado, espaço estará disponibilizado para outros usos e não pode mais ser acessado
- espaço alocado e não liberado explicitamente, será automaticamente liberado ao final da execução
As requisições de memória dinâmica são geralmente
alocadas na área heap.
INSTRUÇÕES PILHA (STACK)
Processo na memória
HEAP
Fenômeno: Stack Overflow
Quando temos variáveis locais muito grande ou muitas chamadas recursivas
INSTRUÇÕES PILHA (STACK)
Processo na memória
HEAP
Como resolver?
Em Java e Python o uso é transparente.
Não precisa se preocupar de alocar e
Endereços e ponteiros
(ferramentas para fazer alocação dinâmica)
Endereços e ponteiros
Os conceitos de endereço e ponteiro são fundamentais em qualquer linguagem de programação.
Na linguagem C é mais visível este conceito.
Requer um esforço para usar os ponteiros.
Endereços
A memória de qualquer computador (arquitetura de von Neumann) é uma sequência de bytes.
Cada byte armazena um de 256 possíveis valores
Os bytes são numerados sequencialmente e o número de um
byte é o seu endereço
Endereços
Endereços
...
01010111 11000011 01100100 11100010
...
37FD00 37FD01 37FD02 37FD03
Geralmente o endereço do objeto é o endereço do 1ro byte.
Endereços e ponteiros
Em C o endereço de um objeto é dado pelo operador &
Se x é uma variável, então &x é o seu endereço
int s = -999
0x89422 -9999
s
Endereços e ponteiros
Em c o endereço de um objeto é dado pelo operador &
Se x é uma variável, então &x é o seu endereço
int s = -9999
int *p = &s //(ou int *p; p=&s;)
0x89422 0x60001
p
p = 0x89422
&p = 0x60001
-9999 0x89422
*p = -9999
s
“p aponta para a s”
“p é o endereço de s”
“p aponta a s”
*p é o mesmo que escrever “s” (*p=``conteúdo apontado por p”)
Endereços e ponteiros
4 8
Os ponteiros são variáveis de 2 a 8 bytes (dependendo
do computador) que armazenam um endereço de memória.
Endereços
Todo ponteiro pode ter o valor NULL.
NULL é uma constante, geralmente vale 0 (definida no arquivo interface stdlib)
0 0x564320
p
int *p = NULL; /* não aponta o dedo pra ninguém */
0x00000
Endereços
Há vários tipos de ponteiros:
P. para caracteres P. para inteiros
P. para registros
P. para ponteiros para inteiros
int* p ; int *p;
int * p;
← O “*” modifica a variável e não o int (mais aceito)
← Um tipo de dado novo int* (conceitualmente correto)
exemploPonteiro1.c
Operadores unarios
& → Referência: na frente de uma variável:
Devolve o endereço de memória onde a variável está armazenada
* → Derreferência: na frente de variável ou expressão:
Devolve o valor ou conteúdo do endereço de memória apontada pela variável ou expressão
exemploPonteiro2.c
Conteúdo apontado pelo endereço de i
exemploPonteiro3.c
Passagem de parâmetros por valor:
exemploPonteiro3.c
Passagem de parâmetros por referência:
exemploPonteiro3.c
Por que o código abaixo está errado?
exemploPonteiro3.c
Por que o código abaixo está errado?
Porque temp é um ponteiro para o qual não foi atribuído um endereço
“que conhecemos”: ele contém um endereço de memória que não pertence a nenhuma das variáveis que criamos.
Vetores e endereços
Vetores
Os elementos de um vetor são alocados consecutivamente na memória do computador.
Se cada elemento ocupa b bytes, a diferença entre os endereços de dois elementos consecutivos será de b.
(ex. inteiros ocupam 4 bytes, em uma plataforma de 64 bits)
Vetores
Os elementos de um vetor são alocados consecutivamente na memória do computador.
Se cada elemento ocupa b bytes, a diferença entre os endereços de dois elementos consecutivos será de b.
O compilador C cria a ilusão de que b vale 1 qualquer que seja o tipo dos elementos do vetor.
Vetores e ponteiros
Vetores e ponteiros
vetor
...
...
Vetores e ponteiros
...
...
Alocação dinâmica
(void *) malloc( size );
size: tamanho (número de bytes a alocar)
malloc aloca um bloco de bytes consecutivos na memória heap
void * : endereço devolvido por malloc
void * : ponteiro do tipo void é utilizado para referenciar
um tipo genérico, que pode ser transformado em qualquer
outro tipo.
Alocação dinâmica
Alocação dinâmica
Alocação dinâmica
Alocação dinâmica
Ideia V+i*sizeof(int)
Alocação dinâmica
Quando não for possível Separar memoria suficiente Um ponteiro NULO é devolvido
A função malloc não assegura que o espaço de
memória foi realmente alocado, por isso é importante verificar se o ponteiro é diferente de NULL antes usá-lo!
Quando não for possível Separar memoria suficiente Um ponteiro nulo é devolvido
A diferença de ponteiros
Alocação dinâmica
uma vez alocado o espaço, não existe garantias do
valor inicial dos elementos da array!!
Os ponteiros facilitam a alocação dinâmica
de memória
Após o uso e antes de terminar o programa, devemos liberar a memória para que ela seja utilizada por outros
Matrizes
Material adaptado da aula de Matrizes de Ronaldo F. Hashimoto e Carlos H. Morimoto (IME/USP)
Declara uma matriz M de 100 linhas com 200 colunas
(20mil inteiros)
Estrutura da matriz na memória do computador
Disposição dos 20mil elementos da matriz M na memória
Disposição dos 20mil elementos da matriz M na memória
Qual o endereço de M[0][78]?
(tendo como base M[0][0]) &M[0][0]+78
Disposição dos 20mil elementos da matriz M na memória
Disposição dos 20mil elementos da matriz M na memória
Qual o endereço de M[78][21]?
(tendo como base M[0][0]) &M[0][0] + (78*200+21)
Índices
Na linguagem C não existe verificação de índices fora da matriz/vetor.
Quem deve controlar o uso correto dos índices é o programador.
O acesso utilizando um índice errado pode ocasionar o acesso de outra variável na memória.
→ Se o acesso à memória é indevido você recebe a mensagem “segmentation fault”.
Matrizes
Matrizes
Matrizes
A alocação dinâmica de memória para matrizes é realizada da mesma forma que para vetores.
A diferença é que temos um ponteiro apontando para outro ponteiro que aponta para o “valor final”.
Ou seja é um ponteiro para ponteiro (indireção múltipla).
A indireção múltipla pode ser levada a qualquer
dimensão desejada, mas raramente é necessário mais de um ponteiro para um ponteiro.
Matrizes
A estrutura de dados utilizada é composta por um vetor de ponteiros (correspondendo ao
primeiro índice da matriz), sendo que cada ponteiro aponta para o início de uma linha da matriz.
Em cada linha existe um vetor alocado
dinamicamente, como descrito anteriormente
(compondo o segundo índice da matriz).
Matrizes
Matrizes (bidimensionais) são implementadas como vetores de vetores.
Exemplo de trecho de código para alocar uma
matriz M com m linhas e n colunas:
Atividade em aula
Questão 1 - a
Escreva o resultado da execução do seguinte programa
Questão 1 - b
Escreva o resultado da execução do seguinte programa
Questão 1 - c
Função com matriz como parâmetro
O nome de uma matriz dentro do parâmetro de uma função é utilizado como sendo um ponteiro para o primeiro
elemento da matriz que está sendo usada na hora de utilizar a função.
→ Não é alocada memória (novamente) para um vetor passado por parâmetro para uma função.
Questão 2
Escreva um programa que leia um número inteiro positivo n seguido de n números inteiros e imprima esses n números em ordem
invertida.
Por exemplo, ao receber 5
22 33 44 55 66
o seu programa deve imprimir 66 55 44 33 22
Seu programa não deve impor limitações sobre o valor de n