• Nenhum resultado encontrado

Implementação de Alocação Dinâmica

No documento Introdução ao C em 10 aulas (páginas 157-163)

nho fixo para o vetor, 1000 por exemplo, ele pode declarar o vetor com um tamanho

curinga, que permitirá esse dimensionamento conforme a demanda. Nesta aula, serão

apresentados os conceitos que permitirão entender como fazer isso na prática.

Antes de começar é importante entender na prática como isso ocorre em termos computacionais. É muito simples, como já mencionado, deve-se utilizar um ponteiro para fazer a alocação dinâmica, pois ao requisitar um espaço de memória ao sistema operacional em tempo de execução usando um ponteiro, ele ira devolver para o pro- grama o endereço do início deste espaço de memória que foi alocado (BACKES,2013;

EDELWEISS; LIVI,2014).

A figura26mostra um exemplo em que foi requisitada a alocação de 5 posições do tipo int ao sistema operacional. Veja que inicialmente o ponteiro é declarado e o seu conteúdo aponta para NULL, posteriormente o sistema operacional devolve para o ponteiro o endereço do primeiro espaço de memória reservado, assim, o ponteiro passa a se comportar como um vetor de tamanho 5, em que, em cada posição, podem ser armazenados valores, assim como em um vetor. A mesma dinâmica vale para matrizes também.

Figura 26 – Representação didática de alocação dinâmica

Fonte: Adaptado de (BACKES,2013, p. 223)

10.3

Implementação de Alocação Dinâmica

Para implementar um programa com recursos de alocação dinâmica, em geral, 5 fun- ções da biblioteca stdlib.h serão úteis:

• malloc • calloc • realloc

154 Aula 10. Alocação Dinâmica de Memória

• free • sizeof

Cada função tem seu papel, desta forma, é apresentado a seguir uma descrição breve sobre cada função, e após a definição de cada uma, são apresentados alguns exemplos de implementação.

10.3.1

Função sizeof()

A função sizeof() não lida propriamente com alocação dinâmica, mas ela é muito útil ao utilizar as demais funções, por isso, tratou-se dela primeiro. A função sizeof() possui a capacidade de retornar o tamanho que um tipo de dado ocupa na memória, isso é muito útil, pois como já dito o tamanho que um tipo ocupa na memória pode variar de acordo com o tipo e o compilador, a tabela10mostra, por exemplo, a variação de tamanho entre os tipos.

Quando alocamos um espaço de memória dinamicamente, fazemos isso para um tipo de dado primitivo, conforme a tabela1, ou um tipo definido pelo programador1, em ambos os casos, é preciso saber qual o tamanho que este determinado tipo ocupa em memória. Como este tamanho pode variar entre compiladores da linguagem C, a função sizeof() torna essa questão um mero detalhe, pois ao utilizar a função para determinar o tamanho do tipo, o programa se torna independente de compiladores e arquitetura. A seguir um exemplo de uso da função.

1 #include <stdio.h>

2 #include <stdlib.h>

3

4 void main()

5 {

6 int num, tamanho;

7 char letra;

8 tamanho = sizeof(num);

9 printf("Tamanho em bytes do inteiro: %d \n", tamanho);

10

11 tamanho = sizeof(letra);

12 printf("Tamanho em bytes do char: %d \n", tamanho);

13 }

Veja que, na linha 8 a função sizeof() foi utilizada para obter o tamanho em bytes da variável num do tipo int, conforme pode ser visto na linha 6. Após obter o tamanho, o valor é impresso na linha 9. Novamente a função é invocada na linha 11, desta vez para obter o tamanho da variável letra do tipo char. Os dois tipos foram utilizados ao invocar a função para mostrar que ela é capaz de obter o tamanho de qualquer variável de qualquer tipo. Veja também que a forma de uso da função é bem simples, basta informar, a variável que deseja obter o tamanho, como argumento e a função irá retornar o tamanho em bytes.

10.3.2

Função malloc()

A função malloc() é uma das funções responsáveis pela alocação de memória em tempo de execução. É a mais utilizada. O modo de funcionamento dela é: solicita ao sistema

10.3. Implementação de Alocação Dinâmica 155

operacional a alocação de memória e retorna um ponteiro com o endereço do início da área de memória reservada (BACKES,2013, p. 224). A seguir a sintaxe da função

malloc().

1 //Sintaxe:

2 #include <stdlib.h>

3 void *malloc(unsigned int num);

Conforme pode ser visto na sintaxe, a função malloc() recebe um parâmetro de entrada, num, referente ao tamanho do espaço de memória que deve ser alocado. Além disso, a função retorna um ponteiro para a primeira posição do vetor alocado ou NULL, caso ocorra algum erro. Note que, o retorno da função malloc() é um ponteiro genérico (void*), pois ela não sabe que será feito com a memória alocada (BACKES,2013, p. 225). Veja a seguir um exemplo de uso da função.

1 #include <stdio.h> 2 #include <stdlib.h> 3 4 void main() 5 { 6 int *p; 7 p = malloc(10 * 4); 8 }

Na linha 6 foi declarado um ponteiro do tipo int, neste caso, ao alocar a memória deve-se preocupar com o tamanho em bytes necessário para o tipo inteiro. Na linha 7 foi utilizada a função malloc() para alocar memória para p, note que o argumento num da função, em que deve-se informar o tamanho necessário de memória para alocar, recebeu os valores 10 * 4, poderia ter sido informado simplesmente 40, mas o objetivo foi destacar que está sendo solicitado 10 * 4 bytes, sendo 4 bytes o tamanho necessário para o inteiro, então, o resultado será um vetor de inteiro com tamanho 10. Por isso, a função sizeof() é muito útil, pois como saber que o inteiro requer 4 bytes? Veja a seguir o exemplo ajustado com a função sizeof().

1 #include <stdio.h> 2 #include <stdlib.h> 3 4 void main() 5 { 6 int *p;

7 p = malloc(10 * sizeof(int));

8 }

Note que na linha 7, o valor 4, referente aos bytes foi substituído pelo código si-

zeof(int), este código irá retornar o tamanho correto em bytes para qualquer tipo,

independente de compilador ou arquitetura, isso trará a característica de portabilidade ao código. Como já mencionado, a função malloc() retorna um ponteiro genérico, pois ela não sabe o que pretende-se fazer com a memória alocada, desta forma, é impor- tante garantir que a memória alocada suportará o tipo desejado, para isso, basta fazer a conversão do retorno da função malloc(). Veja a seguir o exemplo ajustado com a conversão.

1 #include <stdio.h>

2 #include <stdlib.h>

156 Aula 10. Alocação Dinâmica de Memória

4 void main()

5 {

6 int *p;

7 p = (int*) malloc(10 * sizeof(int));

8 }

Novamente, a mudança ocorreu na linha 7, note que agora foi adicionado o código

(int*) antes da função malloc(), esse código fará a conversão do tipo genérico para int,

naturalmente, os exemplos com o tipo int, podem ser adaptados para outros tipos, apenas trocando o operador de tipo. Outro detalhe já mencionado, é que, em caso de erro ao alocar a memória, a função malloc() retornará NULL, assim, é fácil verificar, antes de tentar utilizar o ponteiro, se a memória foi alocada com sucesso. Veja a seguir um exemplo. 1 #include <stdio.h> 2 #include <stdlib.h> 3 4 void main() 5 { 6 int *p;

7 p = (int*) malloc(10 * sizeof(int));

8 if (p == NULL) {

9 printf("Erro: Memoria insuficiente!\n");

10 }

11 else {

12 int i;

13 for (i=0; i<10; i++) {

14 printf("Informe um valor: \n");

15 scanf("%d", &p[i]);

16 }

17 }

18 }

Observe que da linha 8 em diante, foram adicionadas instruções para exemplificar o tratamento em caso de erro ao alocar memória. Na linha 8 foi adicionada uma condição que verifica se p == NULL, pois caso esta verificação seja verdadeira, significa que não houve sucesso ao alocar a memória, neste caso, será executada a linha 9 que imprime uma mensagem de erro. Se a verificação da linha 8 não retornar erro, então as linhas 12 à 16 serão executadas.

10.3.3

Função calloc()

A função calloc() tem o mesmo papel da função malloc(), ou seja, alocar memória dinamicamente. Contudo, há duas diferenças, primeiro, a função calloc() requer dois argumentos, o que torna explicito o tamanho requisitado para a memória e o tamanho de cada bloco da memória requisitada, segundo, a função calloc() inicializa os blocos de memória alocados de acordo com o tipo, se o tipo é inteiro, por exemplo, então cada bloco será inicializado com 0 (zero). Isso, naturalmente, requer mais esforço da função

calloc(), desta forma, para grandes espaços de memória alocados, a função malloc()

apresenta um melhor desempenho. A seguir a sintaxe da função calloc().

1 //Sintaxe:

2 #include <stdlib.h>

10.3. Implementação de Alocação Dinâmica 157

Conforme a sintaxe, a função calloc() recebe dois parâmetros de entrada, num, referente ao tamanho do espaço de memória que deve ser alocado e size referente ao tamanho de cada bloco da memória a ser alocada. Assim como a função malloc(),

calloc() retorna um ponteiro para a primeira posição do vetor alocado ou NULL, caso

ocorra algum erro. Veja a seguir um exemplo de uso da função.

1 #include <stdio.h> 2 #include <stdlib.h> 3 4 void main() 5 { 6 int *p;

7 p = (int*) calloc(10, sizeof(int));

8 if (p == NULL) {

9 printf("Erro: Memoria insuficiente!\n");

10 }

11 else {

12 int i;

13 for (i=0; i<10; i++) {

14 printf("Informe um valor: \n");

15 scanf("%d", &p[i]);

16 }

17 }

18 }

Veja que a linha 7, do código-fonte de exemplo, é a responsável pela invocação da função calloc(), note que a diferença é que neste caso, são dois argumentos, ao invés de multiplicar 10 pelo tamanho do bloco de memória, como é feito com a função

malloc(), neste caso, informa-se 10 como primeiro argumento, e o tamanho do bloco

de memória como segundo argumento. Outro ponto a ser observado é que, a função também retorna um NULL, caso ocorra algum erro ao alocar a memória, permitindo assim verificar se houve sucesso, como pode ser visto na validação que é realizada a partir da linha 8.

10.3.4

Função realloc()

A função realloc() é capaz de alocar ou realocar blocos de memória já alocados pelas funções malloc(), calloc() ou a própria função realloc(). Mas o que é realocar? Basica- mente é mudar o tamanho da memória que já foi alocada, imagine a situação em que você alocou memória, mas agora você precisa alocar mais, pois a memória anterior está acabando, então neste caso, utilize realloc(). Veja a seguir a sintaxe:

1 //Sintaxe:

2 #include <stdlib.h>

3 void *realloc(void *ptr, unsigned int num);

Imagine o seguinte, se a função realloc() tem o papel de realocar blocos de memória já alocados, então o que é primordial informar a esta função para que ela consiga desempenhar o seu papel? Isso mesmo, o ponteiro que aponta para o primeiro bloco de memória que já foi alocada, este é o primeiro argumento da função realloc(), o segundo argumento, é o tamanho, no qual se deseja realocar a memória. Veja a seguir um exemplo de uso da função.

158 Aula 10. Alocação Dinâmica de Memória 1 #include <stdio.h> 2 #include <stdlib.h> 3 4 void main() 5 { 6 int *p;

7 p = (int*) malloc(10 * sizeof(int));

8 if (p == NULL) {

9 printf("Erro: Memoria insuficiente!\n");

10 }

11 else {

12 int i;

13 for (i=0; i<10; i++) {

14 printf("Informe um valor: \n");

15 scanf("%d", &p[i]);

16 }

17 }

18 //aumentando o tamanho da memoria

19 p = realloc(p, 20 * sizeof(int));

20

21 if (p == NULL) {

22 printf("Erro: Memoria insuficiente!\n");

23 }

24 else {

25 int i;

26 for (i=0; i<20; i++) {

27 printf("Valor na posicao %d: %d\n", i, p[i]);

28 }

29 }

30 }

Como pode ver no código-fonte de exemplo, na linha 7 a memória foi normalmente alocada com a função malloc(), sendo um vetor do tipo int com tamanho 10, esta área é então utilizada e depois é realocada com tamanho 20 pela função realloc(), como pode ser visto na linha 19. Note que a função realloc() requer dois argumentos, o primeiro é o próprio ponteiro p, que já apontava para um bloco de memória reservada e o segundo argumento é o novo tamanho, no qual essa memória já reservada, deve passar a ter. Depois de realocada a memória, o ponteiro pode novamente ser utilizado normalmente como um vetor, como pode ser visto a partir da linha 21. Um detalhe importante sobre a função realloc() é que, ao realocar blocos de memória já previamente alocados, os dados já gravados naquele trecho não serão perdidos. Além disso, a função realloc() também é capaz de diminuir o tamanho da memória alocada, mas neste caso, dados podem ser perdidos.

10.3.5

Função free()

A função free() possui um papel muito importante, o de liberar os espaços de memória alocados dinamicamente, para uso de outros programas ou processos. Isso é necessário, pois diferente das variáveis declaradas de forma estática, a memória alocada dinamica- mente não é liberada automaticamente pelo programa, mesmo após este ser encerrado. Assim, o sistema operacional não tomará conhecimento de que aquela área de memória está novamente disponível para uso. Veja a seguir a sintaxe da função free().

No documento Introdução ao C em 10 aulas (páginas 157-163)