Alocação Dinâmica de Memória

O objetivo dessa unidade é apresentar o conteúdo relacionado à alocação dinâmica. Será feita uma abordagem focando na linguagem C.

Alocação Estática

Na alocação estática de memória, os tipos de dados têm tamanho predefinido. Neste caso, o compilador vai alocar de forma automática o espaço de memória necessário. Sendo assim, dizemos que a alocação estática é feita em tempo de compilação.

Problemas:

  • Desperdiçar recursos: nem sempre é possível determinar previamente qual é o espaço necessário para armazenar as informações. Quando não se conhece o espaço total necessário, a tendência é o programador exagerar pois é melhor superdimensionar do que faltar espaço. Por exemplo alocar um vetor de 100 caracteres para armazenar o nome de uma pessoa, porém no momento só foram utilizados 30.
  • Falta de recursos: caso você aloque um vetor de 10 caracteres e escreva 20 para armazenar o nome de uma pessoa, verá que isso é possível. Porém, isso é extremamente falho e perigoso, pois os caracteres sobressalentes irão ocupar outros endereços de memória que você não alocou, e nesses espaços de memória poderiam existir informações importantes de seu computador.

Alocação Dinâmica

Na alocação dinâmica podemos alocar espaços durante a execução de um programa, ou seja, a alocação dinâmica é feita em tempo de execução. Isto é bem interessante do ponto de vista do programador, pois permite que o espaço em memória seja alocado apenas quando necessário. Além disso, a alocação dinâmica permite aumentar ou até diminuir a quantidade de memória alocada.

Então, por exemplo, se uma pessoa vai digitar 10 caracteres, armazene isso numa string de tamanho 11 (tem que ter o caractere limitador \0).

Se uma funcionária de uma farmácia vai cadastrar 20 medicamentos, seu aplicativo C deve alocar somente o espaço para as estruturas desses 20 medicamentes.

malloc()

malloc(), de Memory Allocation, é uma função da biblioteca stdlib.h que recebe como argumento números inteiros positivos que irão representar o número de bytes que iremos alocar.

Essa função retorna um ponteiro contendo o endereço do bloco alocado.

Sua sintaxe é:

Como já visto os ponteiros precisam saber para que tipo de variável vão apontar, pois (dentre outras coisas), podemos precisar utilizar a aritmética de ponteiros.

Porém, a função malloc() serve para declarar qualquer tipo de dado, seja int, float, double ou uma struct criada por você. Ela irá retornar o endereço do bloco de memória que foi alocado. Então, ao passo que fazemos essa alocação, devemos fazer um cast (conversão), ou seja, converter o ponteiro retornado por malloc para um ponteiro de um determinado tipo predefinido, para que com isso um outro ponteiro (de algum tipo já definido) declarado possa receber esse endereço (lembrando que um ponteiro contém um endereço de memória).

A sintaxe fica da seguinte maneira:

Como exemplo, vamos alocar 30 bytes para armazenar um nome que será digitado pelo usuário:

sizeof()

Para se manter uma boa prática de programação é importante que evitemos o uso de números para escolher o número de bytes alocados, isso se deve ao fato de que um tipo variável pode ter diferentes tamanhos, dependendo da arquitetura. Por exemplo um inteiro que pode ocupar em algumas máquinas 2 bytes e outras 4 bytes.

Então, para eliminarmos essa dúvida utilizamos a função sizeof().

sizeof() é usado para identificar o tamanho de variáveis ou de tipos . Ele retorna o tamanho do tipo ou variável em bytes.

Sintaxe:

Por fim, unimos então malloc() e sizeof()da seguinte maneira:

O modelo acima alocará espaço na memória para uma variável do tipo predefinido. Porém, caso for necessário alocar espaço para mais de uma variável de determinado tipo (matrizes, vetores), indicamos quantas variáveis necessárias fazendo uma multiplicação do número de variáveis vezes sizeof(tipo_predefinido).

Agora vamos refazer o exemplo do nome com a utilização de sizeof(), porém agora queremos alocar um espaço de 30 caracteres para armazenar o nome:

calloc()

A função calloc() também serve para alocar memória, mas possui um protótipo um pouco diferente:

Ou da maneira mais correta:

A função calloc reserva um bloco com o tamanho (numero_de_elementos) * (numero_de_bytes).

Quando usamos a calloc(), além de reservar esse espaço de memória ele muda os valores contidos nesses bytes, colocando todos para 0, é como se inicializássemos uma variável com o valor 0 ou todas posições de um vetor com 0.

Para exemplificar, vamos fazer o cálculo do quadrado dos n primeiros números(a partir de 1) utilizando calloc():

free()

Se você alocar muita memória, poderá chegar uma hora que não vai ter mais nenhum byte disponível para uso e seu programa vai simplesmente parar.

Geralmente, isso ocorre por uma falha de programação, uma maneira simples de acontecer isso é ficar alocando memória dentro de um looping e não liberá-la antes do término desse looping.

Em funções em C, quando criamos uma variável dentro do escopo da função, é como se ela não existisse fora dela. Então, podemos criar um ponteiro, alocar memória e fazer o ponteiro armazenar o endereço da memória alocada. Se não liberarmos a memória antes da função acabar, ela vai ficar eternamente alocada e inacessível, pois 'perdemos' o ponteiro quando a função terminou.

A solução para este tipo de problema é simples, basta usar a função free(), que vai liberar o espaço de memória que foi previamente alocado. Assim como outras funções de alocação dinâmica de memória, esta função também está na biblioteca stdlib.h

Ela recebe um ponteiro, o que foi usado para receber o endereço do bloco de memória alocada, e não retorna nada.

Ou seja, sua sintaxe é bem simples:

Para exemplificar, vamos fazer um programa que resolve um possível problema de alocação dentro de looping. Ele terá uma função que aloca 1000 bytes para armazenar inteiros e que, após executá-la, seja chamada uma função libera que, com a utilização de free(), liberará o espaço alocado. Esse processo deverá acontecer n vezes:

realloc()

Imagine um programa que faça a média de n números. A cada iteração, ele adiciona mais dois números para essa operação, ou seja, na segunda iteração será a média n + 2 números e assim sucessivamente.

Para a primeira iteração n é igual a 10, você aloca o espaço para os 10 números com o uso do malloc (). Na próxima iteração, ele pede a média de 12 números, então você libera o anterior e aloca outro bloco novamente com malloc().

Bem, não seria mais fácil para o computador simplesmente alocar mais 2 espaços? Quem sabe podem existir mais 2 blocos de memória ali, logo ao lado daqueles 10.

Portanto, nem sempre a malloc() é a solução mais eficiente e produtiva, tais problemas podem ser contornados, através do uso da função realloc().

A função realloc() como o próprio nome diz, faz com que um espaço de memória seja realocado. Para que isso aconteça é necessário realocar algo que tenha sido alocado. Então, podemos concluir que para usa-lá é necessário ter um ponteiro que foi usado para alocar um espaço de memória.

realloc(), assim como a malloc(), retorna um endereço com um novo bloco de memória.

Seja 'ptr' esse ponteiro, a sintaxe para o uso da função realloc() é:

O 'numero_bytes' é o número de bytes que queremos realocar. É exatamente da mesma maneira que fizemos com malloc(), onde geralmente fazemos 'n * sizeof(tipo_predefinido)'.

Outra coisa que devemos lembrar é que um ponteiro deve receber o endereço da realloc(), e quem recebe é o mesmo ponteiro utilizado dentro realloc().

Para exemplificar, vamos fazer uma realocação de um espaço reservado que já contém uma String com o objetivo de adicionar mais espaço e, consequentemente, mais caracteres:

Tela de execução:

Observação: na realocação o espaço alocado também pode ser diminuído.

Video Explicativo - Alocação Dinâmica

Exercícios