O espaço de endereços de um processo em execução é dividido em várias áreas distintas. As mais importantes são:
exec
e permanece do mesmo tamanho durante toda a vida do processo.Um programa em C suporta três tipos de alocação de memória:
Por default, 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, sendo descartadas quando a função encerra. Esse é o caso de n
, i
e soma
no código a seguir:
#include <stdio.h> // calcula a soma de 1 a n int soma_n (int n) { int i, soma = 0 ; for (i = 1 ; i <= n ; i++) soma += i ; return (soma) ; } int main () { printf ("Soma de 1 a 100 = %d\n", soma_n (100)) ; return 0 ; }
Se a função for chamada recursivamente, novas cópias das variáveis locais e parâmetros serão alocados na pilha, em áreas distintas para cada nível de recursão. Isso permite preservar os valores anteriores dos mesmos no retorno dos níveis de recursão. Isso é o que ocorre com o parâmetro n
e a variável local parcial
no código abaixo:
#include <stdio.h> long int fatorial (int n) { long int parcial ; printf ("antes: n: %d\n", n) ; if (n < 2) parcial = 1 ; else parcial = n * fatorial (n - 1) ; // observe a chamada recursiva printf ("depois: n: %d, parcial: %ld\n", n, parcial) ; return (parcial) ; } int main () { printf ("Fatorial (6) = %ld\n", fatorial (6)) ; return 0 ; }
A execução gera o seguinte resultado:
antes: n: 6 antes: n: 5 antes: n: 4 antes: n: 3 antes: n: 2 antes: n: 1 depois: n: 1, parcial: 1 depois: n: 2, parcial: 2 depois: n: 3, parcial: 6 depois: n: 4, parcial: 24 depois: n: 5, parcial: 120 depois: n: 6, parcial: 720 Fatorial (6) = 720
O padrão C99 permite a alocação automática de vetores de tamanho variável, ou seja, definidos em tempo de execução. O exemplo a seguir ilustra esse conceito (que deve ser usado com muito cuidado):
int my_function (int n) { char name[n] ; // aloca string com tamanho "n" ... }
A alocação estática ocorrem com variáveis globais (alocadas fora de funções). Uma variável alocada estaticamente nunca é descartada e mantém seu valor durante toda a vida do programa, exceto quando explicitamente modificada. Exemplo:
#include <stdio.h> // variáveis globais, com alocação estática int v[100] ; int n ; void print_vetor () { for (int i = 0 ; i < n ; i++) printf ("%d ", v[i]) ; printf ("\n") ; } int main () { n = 10 ; print_vetor () ; }
Uma função deve ter parâmetros de definam claramente os dados de entrada e de saída e não deve depender de outros dados além destes.
Uma variável local pode ser alocada estaticamente através do modificador static
. Apesar de estar dentro de uma função, essa variável não será descartada ao final da função e preservará seu valor para a próxima chamada da mesma função. Exemplo:
#include <stdio.h> void acumula (int n) { static int ac = 0 ; // variável local, aloc. estática ac += n ; printf ("acumulado: %d\n", ac) ; } int main () { acumula (3) ; acumula (5) ; acumula (2) ; return 0 ; }
A execução desse código gera a seguinte saída:
acumulado: 3 acumulado: 8 acumulado: 10
Variáveis com alocação estática são alocadas e inicializadas uma única vez durante a execução do programa, portanto seus valores se preservam entre chamadas consecutivas da função.
Na alocação dinâmica, o programa solicita explicitamente áreas de memória ao sistema operacional, as utiliza e depois as libera quando não forem mais necessárias, ou quando o programa encerrar. As requisições de memória dinâmica são geralmente alocadas na área de memória denominada heap.
A alocação dinâmica permite criar variáveis com qualquer tamanho durante a execução do programa, sem precisar definir previamente um tamanho máximo para os dados no código-fonte. Além disso, torna possível a construção eficiente de estruturas de dados complexas, como árvores e grafos.
gcc
gera código que pode alocar memória até 4GB, mesmo em máquinas de 64 bits com mais memória disponível. Para gerar código executável com capacidade para alocar memória dinamicamente além desse limite, devem ser usados flags de compilação específicos, como -mcmodel=medium
ou -mcmodel=large
.
A memória pode ser alocada dinamicamente através da chamada malloc
:
#include <stdlib.h> void * malloc (size_t size)
Esta função aloca uma nova região com size
bytes de tamanho e retorna um ponteiro para o início da mesma (ou 0 em caso de erro). O conteúdo dessa nova área é indefinido (ou seja, pode conter “lixo”).
Exemplo de uso:
struct mystruct *ptr; ... ptr = malloc (sizeof (struct mystruct)); // caso a alocação não tenha ocorrido if (!ptr) { printf ("erro ao alocar memória\n") ; exit (1) ; } // ou ptr = malloc (sizeof (struct mystruct)); if (!ptr) abort () ;
A chamada free
deve ser invocada para liberar uma área de memória previamente alocada dinamicamente:
#include <stdlib.h> void free (void *ptr)
Esta função libera um bloco de memória previamente alocado, apontado por ptr
. É importante observar que o ponteiro ptr
continua apontando para a área liberada, por isso é aconselhável mudar seu valor para NULL
após a liberação:
ptr = malloc (1024) ; ... free (ptr) ; ptr = NULL ; // não é obrigatório, mas fortemente recomendado
free()
não é obrigatório no final do programa. Contudo, é recomendado utilizá-la sempre, para desenvolver o hábito salutar de sempre liberar uma área alocada.
#include <stdlib.h> void * realloc (void *ptr, size_t newsize)
Esta função redimensiona o bloco previamente alocado apontado por ptr
para o novo tamanho newsize
. Retorna o novo endereço do bloco, que pode ser diferente do anterior, caso tenha sido necessário mudá-lo de lugar (o conteúdo original do bloco é preservado nesse caso ou em caso de erro).
#include <stdlib.h> void * calloc (size_t count, size_t eltsize)
Esta função aloca um bloco de memória de tamanho suficiente para conter um vetor com count
elementos de tamanho eltsize
cada um. O conteúdo do bloco alocado é preenchido por zeros.
Exemplo:
float *v ; int i ; v = calloc (10000, sizeof (float)) ; for (i = 0; i < 10000; i++) v[i] = 1.0 / (i + 1) ;
#include <stdlib.h> void * alloca (size_t size)
Esta função provê um mecanismo de alocação dinâmica semi-automática, ou seja, o bloco é alocado manualmente, mas será liberado automaticamente ao encerrar a função onde ele foi alocado. O valor de retorno da chamada é o endereço de um bloco de tamanho size
bytes, alocado na pilha da função atual (como se fosse uma variável local).
O conteúdo inicial de variáveis não-inicializadas depende da forma como são alocadas. A tabela a seguir resume as principais possibilidades:
tipo de alocação | situação | conteúdo inicial |
---|---|---|
estática | variável global | zeros |
estática | variável local estática | zeros |
automática | variável local | indefinido (“lixo”) |
dinâmica | por malloc() | indefinido (“lixo”) |
dinâmica | por realloc() | indefinido (“lixo”) |
dinâmica | por calloc() | zeros |
semiautomática | por alloca() | indefinido (“lixo”) |
Os blocos alocados pelas funções acima geralmente iniciam em um endereço múltiplo de 8 bytes em plataformas de 32 bits. Caso seja necessário garantir a alocação alinhada, obtendo blocos iniciando em múltiplos de 8, 16, 32, 64 bytes, etc, a função a seguir está disponível:
#include <malloc.h> void * memalign (size_t boundary, size_t size)
Aloca um bloco de tamanho size
cujo endereço inicial é um múltiplo de boundary
(que deve ser 2n). Retorna o endereço do bloco alocado, que pode ser liberado mais tarde através da função free
. Ver também a função posix_memalign
.
Muitos programadores implementam funções de “alocação segura” da memória, que verificam se a alocação foi realizada e inicializam a área alocada. Isso evita conteúdo indefinido na memória e dispensa a necessidade de testar cada retorno de alocação. Uma implementação simples de funções malloc
e free
seguras seria:
// malloc seguro: testa se a alocação funcionou e limpa a área alocada. // recebe como entrada o tamanho da área a alocar. void* safe_malloc (unsigned long size) { void* ptr ; // aloca uma área ptr = malloc (size) ; // testa a alocação if (! ptr) { fprintf (stderr, "malloc of %ld bytes failed\n", size) ; exit (1) ; } // preenche a área com zeros (opcional) memset (ptr, 0, size); return (ptr) ; } // free seguro: libera a área alocada e zera o ponteiro, evitando double free. // recebe como entrada o ENDEREÇO do ponteiro da área alocada. void safe_free (void* ptr) { // para evitar o cast (void**) ao chamar safe_free (...) void** ptr_aux = (void**) ptr ; if (ptr_aux && *ptr_aux) { free (*ptr_aux) ; *ptr_aux = NULL ; } } // exemplo de uso: int* vet ; vet = safe_malloc (10000 * sizeof (int)) ; ... safe_free (&vet) ;
v
com n inteiros, o preencha com v[i] = 100*i
, imprima o conteúdo do vetor (os inteiros) e por fim libere a área alocada. O número de elementos do vetor (n) deve ser lido do teclado.v
com n ponteiros para inteiros, aloque e preencha cada inteiro com v[i] = 100*i
, imprima o conteúdo do vetor (os inteiros) e por fim libere todas as áreas alocadas. O número de elementos do vetor (n) deve ser lido do teclado.m
e a preencha com m[i][j] = i+j
, sendo que o número de linhas e colunas são lidos do teclado. A área de memória alocada deve ser definida em função do tamanho da matriz. Este exercício não é tão simples quanto parece, veja sugestões de resolução nesta página.Mais exercícios