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:
- Alocando elemento a elemento conforme formos introduzindo temperaturas
- Alocando um x número de elementos de cada vez sempre que a capacidade do array chegar ao limite
- 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.
Valeu ae mulecada mt bom
verdade mt bom msm