Home > Tecnologia > Informática > Programação > C++ > Funções malloc e realloc em C / C++

Funções malloc e realloc em C / C++

Num artigo anterior – “Como usar apontadores em C / C++” – fiz uma pequena introdução sobre apontadores em C. Agora vou abordar as funções malloc e realloc.

Por vezes, temos a necessidade de trabalhar com arrays dinâmicos ou outro tipo de estruturas de dados que nos levam a ter que usar apontadores. Neste artigo, vou focar essencialmente a questão de arrays dinâmicos.

Comecemos com um exemplo simples e imagine-se que se pretende ir registando a média de temperaturas diárias num determinado local e arredondadas para valores inteiros. Para tal, pretende-se ter um array de números inteiros para irmos armazenando as temperaturas. Repare que poderíamos pensar em fixar um limite para o array, por exemplo para 365 dias. Assim, iríamos declarar o array da seguinte forma:

    int t[365];

Mas, neste caso, passado um ano de registos de temperaturas, o programa atingia o limite do array e não podíamos continuar a introduzir mais temperaturas. A solução mais adequada será, então, usar um apontador!

    int *t;

Agora, qual a melhor solução para irmos alocando memória para o array? Podemos pensar nas seguintes soluções:

  1. Alocando elemento a elemento conforme formos introduzindo temperaturas
  2. Alocando um x número de elementos de cada vez sempre que a capacidade do array chegar ao limite
  3. Começar com uma capacidade inicial de x e ir duplicando a capacidade sempre que esta chegar ao limite

Por vezes, a solução 1 parece ser a mais confortável, pois evita estarmos a testar a capacidade do array. Se vamos introduzir uma nova temperatura, alocamos espaço para mais um valor e assim sucessivamente. Contudo, não é a forma mais elegante de o fazer e estamos constantemente a pedir ao sistema que nos aloque mais memória. Já a solução 2 parece ser mais razoável e a solução 3 é, normalmente, a mais usada. Porém, tudo depende um pouco dos objectivos da implementação e da forma como a quantidade de informação vai crescendo.

Para este exemplo, vou adoptar a solução 2 e considerar que o array vai começar com uma capacidade de 10 e que esta vai aumentando de 10 em 10, conforme necessário. Para tal, vamos usar uma variável “indice” para ir contando as temperaturas introduzidas, uma variável “capacidade” que irá controlar a capacidade do array e o array de temperaturas:

    int indice = 0;
    int capacidade = 10;
    int *temperaturas = NULL;
    
    temperaturas = (int *) malloc(sizeof(int)*capacidade);

Repare que, ao alocarmos espaço para o array usámos o seguinte cálculo: “sizeof(int)*capacidade”. A função “sizeof” retorna o tamanho de um tipo ou estrutura de dados. Neste caso, poderíamos colocar 4, pois é o quanto cada número inteiro ocupa na memória. Mas para não estarmos a decorar estas coisas, usemos o “sizeof”. Por fim, multiplicando pela capacidade, temos o número de bytes que precisamos para alocar os 10 primeiros elementos do nosso array. A função “malloc” vai então alocar esta memória e devolver um apontador para o primeiro endereço de memória. Como a função “malloc” devolve um apontador genérico, temos que fazer o “cast” para um apontador do tipo “int” com (int *).



Vamos agora ver como escrever o código para ler uma temperatura e verificar a capacidade do array:

    int t;
    printf("Introduza a temperatura: ");
    scanf("%d", &t);
    temperaturas[indice] = t;
    indice++;
    if (indice>=capacidade) {
        capacidade+=10;
        temperaturas = (int *) realloc(temperaturas, sizeof(int)*capacidade);
    }

Neste caso, quando o índice atingir a capacidade do array, vamos alocar mais memória e teremos que usar a função “realloc”. Para tal, temos que indicar o apontador para a nossa alocação de memória anterior para que a função “realloc” saiba onde alocar mais memória. Por fim, devolve um novo apontador para a nova zona de memória. Esta pode ser a mesma, mas como a utilização da memória é dinâmica, a função “realloc” poderá receber do sistema um novo endereço de memória e vai copiar os dados da alocação anterior para a nova (daí a razão de termos que passar, como argumento, o apontador com a alocação anterior). Por outro lado, se usássemos novamente a função “malloc”, iríamos ter uma nova alocação de memória mas os dados anteriores seriam perdidos!

E nunca esquecer que, no fim do programa, quando usamos estas funções, temos que libertar a memória alocada:

    free(temperaturas);

Caso contrário, o programa sai mas o sistema operativo mantém a memória usada e não a liberta, criando os chamados “memory leaks“!

Vamos agora ver o programa completo:

#include <stdio.h>
#include <stdlib.h>

int main() {
    
    int menu = -1;
    int indice = 0;
    int capacidade = 10;
    int *temperaturas = NULL;
    
    temperaturas = (int *) malloc(sizeof(int)*capacidade);
    
    while (menu!=0) {
        
        printf("1 - Inserir temperatura\n");
        printf("2 - Listar temperaturas\n");
        printf("3 - Estado do array\n");
        printf("0 - Sair do programa\n");
        scanf("%d", &menu);
        
        switch (menu) {
            case 0:
                break;
            case 1:
                int t;
                printf("Introduza a temperatura: ");
                scanf("%d", &t);
                temperaturas[indice] = t;
                indice++;
                if (indice>=capacidade) {
                    capacidade+=10;
                    temperaturas = (int *) realloc(temperaturas, sizeof(int)*capacidade);
                }
                break;
            case 2:
                for (int i=0; i<indice; i++) {
                    printf("Temperatura no dia %d = %d\n", i, temperaturas[i]);
                }
                break;
            case 3:
                printf("Elementos introduzidos: %d\n", indice);
                printf("Capacidade do array: %d\n", capacidade);
                break;
            default:
                printf("Opção inválida!");
                break;
        }
        
    }
    
    free(temperaturas);
    
    return 0;
    
}

Temos então um programa que exibe um menu para podermos introduzir temperaturas, listar temperaturas, consultar o estado do array, afim de vermos quantos elementos foram introduzidos e a capacidade deste, e para sair do programa. Repare na forma simples como acedemos a cada elemento! Acedemos tal e qual como fazemos nos arrays estáticos. É simples entendermos este processo se olharmos para a memória alocada como um conjunto de bytes contínuos. Para aceder a uma determinada posição, e porque o compilador sabe que se trata de números inteiros, ao fazermos “temperaturas[i]” o compilador sabe que deve ir buscar os dados que estão no endereço “i * 4” (com 4 o número de bytes ocupados por cada número inteiro) e somando este valor (também chamado de deslocamento ou offset) ao endereço de memória onde a alocação começa.



Isto é, por exemplo, considere-se que a memória alocada começa no endereço 1000 (registado no apontador “temperaturas”). Assim, temperaturas[0] tem o seu elemento em “0 * 4 + 1000 = 1000”, o elemento temperaturas[1] em “1 * 4 + 1000 = 1004”, o elemento temperaturas[2] em “2 * 4 + 1000 =1008” e assim sucessivamente…

Continue a ler mais no próximo artigo: “Arrays dinâmicos com structs em C++

Este artigo foi escrito de acordo com a antiga grafia.

About Carlos Santos

Frequência em mestrado de Engenharia Electrotécnica e de Computadores. Programador freelancer: Websites, Aplicações Web (JAVA e PHP), J2SE/J2EE, C/C++ e Android. Explicador e formador em informática, matemática e electrotecnia. Formação presencial ou remota através de Skype e Team Viewer. Interesses: Música, áudio, vídeo, ciência, astronomia e mitologia.

Leave a Reply

Your email address will not be published and it is optional.