\documentclass[apostila.tex]{subfiles} \begin{document} \chapter{Alocação dinâmica de memória} \label{cap:alocdin} Toda e qualquer informação que um programa utiliza está localizada na memória. Mas para que um programa possa utilizar uma área de memória para armazenar informação, é necessário que tal área seja previamente alocada, ou seja, é necessário requisitar ao sistema operacional que reserve uma área de memória para o programa e que a proteja, afim de que outros programas não venham a ler/gravar dados na região de memória reservada ao programa em questão. Imagine se um programa que utilizasse, para armazenar um índice de um for, a mesma área de memória que outro programa usaria para armazenar uma entrada do teclado. Ou então, que a mesma área de memória venha a ser utilizada tanto para armazenar dados de um programa quanto para armazenar o código de outro programa em execução. Catástrofes de todos os tipos podem ocorrer em tais circunstâncias e se não houver um gerenciamento de memória por parte do sistema operacional, programar seria um desafio ainda maior, senão inviável. Alocar uma área de memória significa pedir ao sistema operacional que reserve uma área para uso exclusivo do nosso programa. \section{Alocação estática $\times$ alocação dinâmica} Existem duas maneiras de se alocar memória em um programa em C. A primeira maneira é chamada alocação estática. Quando o sistema operacional inicia a execução de um programa, ele aloca três regiões de memória: o segmento de código, o segmento de dados e o segmento de pilha. No segmento de código o sistema operacional coloca o código do programa. No segmento de dados são colocadas as variáveis globais, constantes e variáveis static. No segmento de pilha são armazenadas, entre outras coisas, as variáveis locais das funções do programa. O problema é que o tamanho desses segmentos é fixo (calculado pelo compilador), ou seja, não pode ser mudado durante a execução de um programa. Imagine que um programa, no meio de uma tarefa, necessite ler um arquivo de 2 Mb do disco, processá-lo e devolvê-lo para o disco. Se não for declarado no código do programa um array de 2 Mb de tamanho, não haverá como processar o arquivo. Agora suponha que foi declarado um matriz de 2 Mb e o programa consegue manipular o arquivo. Suponha que esse é um arquivo de configuração e só precisa ser utilizado uma vez durante as 10 horas em que o programa ficou em execução. Como o tamanho dos segmentos é fixo (daí o nome alocação estática), esses 2 Mb de memória alocados estaticamente estariam reservados para o programa, mas não seriam utilizados (um exemplo de programa mal-comportado). Ou seja, o programa estaria retendo 2 Mb de memória que poderiam ser usados por outros programas e essa memória alocada, mas não utilizada, pode trazer problemas, como impedir que se possa executar outros programas por falta de memória. Poderia ser argumentado que pelo menos o programa funciona. Agora suponha que aquele arquivo tivesse seu tamanho aumentado para 2.5 Mb. Seria necessáro alterar no código o tamanho da matriz e recompilar o programa cada vez que mudasse o tamanho do arquivo. O ideal é que esses 2 Mb de memória sejam alocados somente quando forem necessários e sejam liberados para outros programas quando deixassem de ser úteis. É aí que entra a alocação dinâmica: a alocação dinâmica permite que o programa reserve uma área de memória de qualquer tamanho (dentro dos limites do tamanho da memória, é claro) em tempo de execução. Isso quer dizer que o programa/programador/compilador não precisa saber antecipadamente o tamanho do bloco de memória de que o nosso programa precisa. Durante a execução, o programa descobre qual é o tamanho da área de memória que necessita e pede ao sistema operacional para reservar uma área de memória daquele tamanho. O sistema operacional reserva a área requisitada (se possível) e devolve para o programa o endereço do primeiro byte da área de memória alocada. No programa, esse endereço pode ser armazenado em um ponteiro. \section{sizeof} \label{sec:sizeof} Antes de apresentar as funções de manipulação de memória dinâmica, é importante descrever o operador sizeof. O operador sizeof é usado para obter o tamanho, em bytes, de um determinado tipo de dado. A sintaxe geral é: \begin{lstlisting} sizeof(tipo) // ou ainda sizeof(variavel) \end{lstlisting} O sizeof retorna o tamanho do tipo passado como parâmetro ou do tipo da variável passada como parâmetro. Exemplos: \begin{lstlisting} struct coord { int x, y, z; }; struct coord coordenada1; sizeof(struct coord); // obtêm o valor 12 (4 bytes por int ) sizeof(coordenada1); // obtêm o valor 12 (4 bytes por int ) sizeof(int); // obtêm o valor 4 \end{lstlisting} Esse operador é extremamente útil quando é necessário trabalhar com alocação dinâmica de memória porque permite ao programa determinar o quanto de memória deve ser alocado para um determinado tipo de dado. \section{Função malloc()} A função malloc requisita ao sistema operacional para alocar uma área de memória do tamanho especificado. Essa função é extremamente útil para gerar matrizes cujo tamanho não é possível ser definido antes de executar o programa. Além disso, existem estruturas de dados (listas, filas, pilhas, entre outras) que tem tamanho variável e precisam dessa função para serem implementadas. O protótipo da função malloc é: \begin{lstlisting} void *malloc (unsigned int numero_de_bytes); \end{lstlisting} A função recebe como argumento o tamanho em bytes de memória que se deseja alocar e devolve um ponteiro do tipo void* para o primeiro byte da área de memória alocada. Em caso de erro (não há memória suficiente), o valor retornado é NULL. Como um ponteiro do tipo void* não tem tipo definido, pode ser utilizado um casting para especificar que tipo de ponteiro ele deverá ser. Em algumas implementações da linguagem C, o compilador limita a quantidade de memória que o programador pode alocar (ex.: no compilador Borland C 3.0, o máximo é 64Kb). Exemplo: \vspace*{\fill} \begin{lstlisting} #include #include int main(){ int *vetor, tamanho; printf("Digite o tamanho do vetor:"); scanf("%d", &tamanho); vetor = (int*)malloc(sizeof(int)*tamanho); return 0; } \end{lstlisting} Observe que para alocar o tamanho correto para um vetor de int, é necessário multiplicar o número de células que se deseja pelo tamanho do tipo de dado int porque cada célula individual é do tamanho int. \section{Função free()} A função free é a inversa da função malloc, isto é, ela desaloca (libera) uma área de memória previamente alocada pela função malloc. Abaixo temos a declaração da função: \begin{lstlisting} void free (void *memblock); \end{lstlisting} A função recebe um único argumento, o qual é um ponteiro para uma área de memória previamente alocada com malloc. \vspace*{\fill} \textit{Este espaço foi propositalmente deixado em branco. Veja o código completo na próxima página.} \vspace*{\fill} \lstinputlisting{exemplos/alocacao_dinamica.c} \section{Exercícios} 1) Faça uma função gera matriz com os seguintes parâmetros: \begin{lstlisting} int*** geramatriz(int x, int y, int z, int tam); \end{lstlisting} A função deverá alocar uma matriz de dimensão 3, cada dimensão deverá ter tamanho tam e, ao final, a função retorna a matriz. 2) Crie uma função que seja capaz de redimensionar um vetor previamente alocado (vetor de dimensão 1). A função será chamada realoca e receberá os seguintes parâmetros: \begin{lstlisting} int* realoca(int *vetor, int tam, int novo tam); \end{lstlisting} Onde vetor é o vetor que deve ser realocado, tam é o tamanho velho do vetor e novo tam é o novo tamanho. A função deverá ser capaz de preservar o conteúdo de vetor. Se o novo tam for menor que tam, a informação das células excedentes deve ser descartada. \end{document}