Alocação dinâmica de memória em linguagem C
Alocação dinâmica de memória é uma técnica de gestão de memória que pervade praticamente todos os programas de maior dimensão. É por isso um tópico importante na educação dum programador a sério. Este documento procura revêr alguns aspectos relacionados com alocação dinãmica de memória e a sua utilização na alocação de buffers para entrada/saída de dados.
Em linguagem C, o programador tem que fazer a gestão de memória alocada dinamicamente.
A biblioteca de C oferece 2 funções para gestão de memória alocada dinamicamente:
Para uma informação mais pormenorizada sobre estas funções pode consultar as suas man pages:
man 3 malloc e man 3 free
O operador sizeof é particularmente útil para especificar o valor do argumento de malloc(). De facto, a sua utilização torna o código C mais portável, i.e. mais independente da arquitectura, e liberta o programador de ter de saber qual o tamanho dos diferentes tipos ou estruturas de dados. O segmento seguinte, típico da alocação de memória para um vector de n inteiros, ilustra a utilização do operador sizeof:
ptr = malloc(n * sizeof(int));
Note que a memória é um recurso limitado, pelo que um programa não pode alocar a memória que bem entender. Assim, é importante que quando invoca a função malloc() teste se o valor retornado é ou não NULL:
ptr = malloc(n * sizeof(int)); if( ptr == NULL ) { printf(``Out of memory\n''); return NULL; }
Uma propriedade das regiões de memória alocadas dinamicamente é que persistem para além do bloco da linguagem C onde são alocadas. Por exemplo, o segmento de código acima, poderia fazer parte duma função int *get_arr(int n), a qual aloca memória dinamicamente para um vector de n inteiros e inicializa-a em 0:
int *get_array(int n) { int *ptr; int i; /* Allocate memory for integer array */ if( (ptr = malloc(n * sizeof(int))) == NULL ) { printf(``Out of memory\n''); return NULL; } /* Initialize the array elements to 0 */ for( i = 0; i < n; i++) { ptr[i] = 0; } return ptr; }
Note que neste caso, a função que invoca get_array() deverá testar se o valor retornado é ou não NULL. Se não fôr, poderá aceder aos elementos do vector alocado dinamicamente.
Infelizmente, a gestão de memória alocada dinamicamente não é fácil, sendo uma das razões para a relativa falta de produtividade da linguagem C. Há essencialmente 2 tipos de problemas:
De qualquer modo, a sua única defesa contra erros deste tipo é ser extremamente cuidadoso e disciplinado quando usa alocação dinãmica de memória. Certas linguagens de programação, por exemplo Java, encarregam-se elas mesmo da gestão da memória alocada dinamicamente, libertando o programador duma tarefa dada a erros.
Um uso comum de alocação dinâmica de memória é na alocação de buffers que são usados para guardar dados a ler de dispositivos periféricos de entrada ou a escrever em dispositivos periféricos de saída.
De facto, como se deve recordar de SIF, Linux oferece as seguintes chamadas ao sistema para aceder a ficheiros:
Tipicamente há duas alternativas na alocação destes buffers de memória, usar:
char buf[BUFSIZ]; [...] n = read(fd, buf, BUFSIZ);
char *buf; [...] if( (buf = malloc(BUFSIZ)) == NULL) { printf(``Out of memory!\n''); exit(1); } n = read(fd, buf, BUFSIZ);
IMPORTANTE: Um ERRO relativamente comum é passar a read() (ou a outras funções semelhantes) um apontador não inicializado. Por exemplo:
char *buf; n = read(fd, buf, BUFSIZ);Embora sintaticamente este segmento de código esteja correcto, e por isso o compilador não se ``queixe'', compilando sem erros nem avisos, semanticamente está INCORRECTO:
O kernel precisa de um buffer de memória onde porá os dados lidos. Contudo, no segmento acima, reserva-se apenas espaço para a variável buf que é um apontador para carácteres (e por isso ocupa apenas 4 bytes), NÃO se reserva espaço para os dados a ler. A variável buf tem um valor que é lixo (``[...] If they (automatic variables) are not set, they will contain garbage'', K&R, pg. 31). Isto é, buf fica a apontar para uma zona de memória ao acaso, a qual pode ou não ser válida. No primeiro caso, o conteúdo daquela zona de memória pode ser alterado inadvertidamente, resultando um comportamento errático da aplicação. No segundo caso, a aplicação terminará quando tentar aceder a essa zona de memória.
Uma alternativa seria o próprio kernel alocar um buffer com o tamanho necessário dentro de read(). (De facto algumas funções da biblioteca de C, seguem essa estratégia.) Neste caso, o kernel teria que retornar à aplicação não só o número de bytes lidos (como acontece com a chamada ao sistema existente), mas também o endereço do buffer de memória que tiver reservado. Para isso, o protótipo da função read() teria que ser diferente.
This document was generated using the LaTeX2HTML translator Version 99.2beta6 (1.42)
Copyright © 1993, 1994, 1995, 1996,
Nikos Drakos,
Computer Based Learning Unit, University of Leeds.
Copyright © 1997, 1998, 1999,
Ross Moore,
Mathematics Department, Macquarie University, Sydney.
The command line arguments were:
latex2html -t C-malloc -no_navigation -split 0 -show_section_numbers malloc
The translation was initiated by Pedro Souto on 2001-04-04