Programar em C/Gerenciamento de memória
Alocação dinâmica
editarTodos os dados de um programa são armazenados na memória do computador; é muito comum necessitar reservar um certo espaço na memória para poder guardar dados mais tarde. Por exemplo, poderíamos reservar um espaço de 1000 bytes para guardar uma string que o usuário viesse a digitar, declarando um vetor de 1000 caracteres. E se quiséssemos reservar um espaço que só é conhecido no tempo de execução do programa? E se o espaço fosse muito grande, de modo que declarar vetores de tal tamanho seria inconveniente (pois, entre outras coisas, aumenta sem necessidade o tamanho do executável)?
Para solucionar esse problema, existe a alocação dinâmica de memória, que como o nome sugere, é uma maneira de alocar memória à medida que o programa vai sendo executado. As quatro funções relacionadas com a alocação dinâmica serão descritas a seguir.
malloc e free
editarEssas duas funções são as mais básicas para o gerenciamento de memória. malloc é responsável pela alocação de um pedaço de memória, e free é responsável por liberar esse pedaço de memória.
A função malloc() serve para alocar memória e tem o seguinte protótipo:
void *malloc (unsigned int num); void free (void * ptr);
Para alocar um espaço na memória, precisamos fornecer à função malloc o número de bytes desejados. Ela aloca na memória e retorna um ponteiro void * para o primeiro byte alocado. O ponteiro void* pode ser atribuído a qualquer tipo de ponteiro. Se não houver memória suficiente para alocar a memória requisitada a função malloc() retorna um ponteiro nulo.
Para saber o tamanho do bloco a alocar, precisaremos usar o operador sizeof. Ele permite também saber automaticamente o tamanho de structs criadas pelo usuário.
Veja um exemplo de alocação dinâmica:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
/* ponteiro para memória que será alocada */
int *p;
int i;
/* alocar 10 elementos inteiros, ou seja, ( sizeof (int) * 10 ) */
p = (int *) malloc ( sizeof (int) * 10);
if ( p == NULL ) {
printf ("Erro: Não foi possivel alocar memória\n");
exit(1);
}
for(i = 0; i < 10; i++) {
p[i] = i * 2;
printf ("%d\n", p[i]);
}
/* libera a memória alocada por malloc */
free (p);
return 0;
}
Outros exemplos:
int main()
{
int *p, *q;
p = malloc(sizeof(int));
q = p;
*p = 10;
printf("%d\n", *q);
*q = 20;
printf("%d\n", *q);
}
int main()
{
int *p, *q;
p = malloc(sizeof(int));
q = malloc(sizeof(int));
*p = 10;
*q = 20;
*p = *q;
printf("%d\n", *p);
}
- O compilador aceita *p=*q porque são ambos int.
- O compilador aceita também p=q porque ambos são ponteiros e apontam para o mesmo tipo.
- Podemos simplificar p = malloc(sizeof(int)); por p = malloc(4); mas como temos sistemas operacionais de 16,32, 64 bits a primeira declaração torna as coisas mais portáveis.
calloc
editarA função calloc() também serve para alocar memória, mas possui um protótipo um pouco diferente:
void *calloc(size_t nelem, size_t elsize);
A função calloc reserva um bloco com o tamanho (nelem x elsize) octetos consecutivos, isto é, aloca memória suficiente para um vetor de num objetos de tamanho size. Diferente de malloc(), o bloco reservado é inicializado a 0. Essa função retorna um ponteiro void* para o primeiro byte alocado. O ponteiro void* pode ser atribuído a qualquer tipo de ponteiro. Se não houver memória suficiente para alocar a memória requisitada a função calloc() retorna um ponteiro nulo.
Exemplo:
#include <stdio.h>
#include <stdlib.h> /* Para usar calloc() */
int main (){
int *p;
int n;
int i;
... /* Determina o valor de n em algum lugar */
p = calloc(n, sizeof(int)); /* Aloca n números inteiros p pode agora ser tratado como um vetor com n posicoes */
//p = malloc(n*sizeof(int)); /* Maneira equivalente usando malloc. */
if (!p)
{
printf ("** Erro: Memoria Insuficiente **");
exit(0);
}
for (i=0; i<n; i++) /* p pode ser tratado como um vetor com n posicoes */
p[i] = i*i;
...
return 0;
}
No exemplo acima, é alocada memória suficiente para se colocar n números inteiros. O operador sizeof() retorna o número de bytes de um inteiro. Ele é útil para se saber o tamanho de tipos. O ponteiro void * que calloc() retorna é convertido para um int* pelo cast e é atribuído a p. A declaração seguinte testa se a operação foi bem sucedida. Se não tiver sido, p terá um valor nulo, o que fará com que !p retorne verdadeiro. Se a operação tiver sido bem sucedida, podemos usar o vetor de inteiros alocados normalmente, por exemplo, indexando-o de p[0] a p[(a-1)].
realloc
editarA função realloc() serve para realocar memória e tem o seguinte protótipo:
void *realloc(void *ptr, size_t size);
A função realloc ajusta o tamanho de um bloco a size octetos consecutivos. A função modifica o tamanho da memória previamente alocada com malloc, calloc ou realloc e apontada por ptr para o tamanho especificado por size. O valor de size pode ser maior ou menor que o original. Um ponteiro para o bloco é devolvido porque realloc() pode precisar mover o bloco para aumentar seu tamanho. Se isso ocorrer, o conteúdo do bloco antigo é copiado no novo bloco, o bloco antigo é liberado e nenhuma informação é perdida. Se não precisar mover, o valor retornado é igual a ptr. Se ptr for nulo, a função aloca size bytes e devolve um ponteiro, funcionando como malloc(); se size é zero, a memória apontada por ptr é liberada. Se não houver memória suficiente para a alocação, um ponteiro nulo é devolvido e o bloco original é deixado inalterado.
Exemplo:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main() {
char *str1=NULL, *str2=NULL;
str1 = (char *) malloc(11);
strcpy(str1, "ABCDEFGHIJ");
str2 = (char *) realloc(str2, 20);
printf("Endereço de str1 : %p\n", str1);
printf("Endereço de str2 : %p\n", str2);
str1 = (char *) realloc(str1, 100);
printf("Novo endereço de str1 : %p\n", str1);
printf("Conteudo de str1 : %s\n", str1);
free(str1);
free(str2);
return 0;
}
Alocação Dinâmica de Vetores
editarA alocação dinâmica de vetores utiliza os conceitos aprendidos na aula sobre ponteiros e as funções de alocação dinâmica apresentados. Um exemplo de implementação para vetor real é fornecido a seguir:
#include <stdio.h>
#include <stdlib.h>
float *Alocar_vetor_real (int n)
{
float *v; /* ponteiro para o vetor */
if (n < 1)
{ /* verifica parametros recebidos */
printf ("** Erro: Parametro invalido **\n");
return (NULL);
}
v = calloc (n, sizeof(float)); /* aloca o vetor */
if (v == NULL)
{
printf ("** Erro: Memoria Insuficiente **");
return (NULL);
}
return (v); /* retorna o ponteiro para o vetor */
}
float *Liberar_vetor_real (float *v)
{
if (v == NULL) return (NULL);
free(v); /* libera o vetor */
return (NULL); /* retorna o ponteiro */
}
int main (void)
{
float *p;
int a;
... /* outros comandos, inclusive a inicializacao de a
*/
p = Alocar_vetor_real (a);
... /* outros comandos, utilizando p[] normalmente */
p = Liberar_vetor_real (p);
}
Alocação Dinâmica de Matrizes
editarA alocação dinâmica de memória para matrizes é realizada da mesma forma que para vetores, com a diferença que teremos um ponteiro apontando para outro ponteiro que aponta para o valor final, ou seja é um ponteiro para ponteiro, o que é denominado 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. Um exemplo de implementação para matriz real bidimensional é fornecido a seguir. A estrutura de dados utilizada neste exemplo é 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).
#include <stdio.h>
#include <stdlib.h>
float **Alocar_matriz_real (int m, int n)
{
float **v; /* ponteiro para a matriz */
int i; /* variavel auxiliar */
if (m < 1 || n < 1)
{ /* verifica parametros recebidos */
printf ("** Erro: Parametro invalido **\n");
return (NULL);
} /* aloca as linhas da matriz */
v = calloc (m, sizeof(float *)); /*Um vetor de m ponteiros para float */
if (v == NULL)
{
printf ("** Erro: Memoria Insuficiente **");
return (NULL);
}
for ( i = 0; i < m; i++ ) /* aloca as colunas da matriz */
{
v[i] = calloc (n, sizeof(float)); /* m vetores de n floats */
if (v[i] == NULL)
{
printf ("** Erro: Memoria Insuficiente **");
return (NULL);
}
}
return (v); /* retorna o ponteiro para a matriz */
}
float **Liberar_matriz_real (int m, int n, float **v)
{
int i; /* variavel auxiliar */
if (v == NULL) return (NULL);
if (m < 1 || n < 1)
{ /* verifica parametros recebidos */
printf ("** Erro: Parametro invalido **\n");
return (v);
}
for (i=0; i<m; i++) free (v[i]); /* libera as linhas da matriz */
free (v); /* libera a matriz (vetor de ponteiros) */
return (NULL); /* retorna um ponteiro nulo */
}
int main (void)
{
float **mat; /* matriz a ser alocada */
int l, c; /* numero de linhas e colunas da matriz */
int i, j;
... /* outros comandos, inclusive inicializacao para l e c */
mat = Alocar_matriz_real (l, c);
for (i = 0; i < l; i++)
for ( j = 0; j < c; j++)
mat[i][j] = i+j;
... /* outros comandos utilizando mat[][] normalmente */
mat = Liberar_matriz_real (l, c, mat);
...
}