Mostrando postagens com marcador malloc. Mostrar todas as postagens
Mostrando postagens com marcador malloc. Mostrar todas as postagens

domingo, 3 de janeiro de 2010

Funções x Vetores. Parte Final (código)

Agora que já vimos os conceitos e como funciona a alocação dinâmica, estamos prontos para ver um código exemplo. Então vamos a ele.


#include <stdio.h>
#include <stdlib.h>
 
int ler_int(){
  int digitado;
 
 printf("Digite um valor inteiro: ");
 scanf("%d",&digitado);
 return digitado;
}
 
void mostra_int(int num){
 printf("%d ",num);
}
 
void exibe_vetor(int* vet, int tamanho){
 int i;
 
 printf("\n");
 for(i = 0; i < tamanho; i++)
    mostra_int(vet[i]);
 printf("\n\n");
}
 
int* preenche_vet_int(int tamanho){
 int *vet=0, i;
 
 vet = (int *) malloc (tamanho * sizeof(int));
 if (vet){
    printf("\nPREENCHENDO O VETOR:\n");
    for(i = 0; i < tamanho; i++)
       vet[i] = ler_int();
 }
 return vet;
}
 
int aumenta_vet_int(int* vet, int* tamanho, int incremento){
 int novo_tam, i;
 
 novo_tam = *tamanho + incremento;
 vet = (int *) realloc (vet, novo_tam * sizeof(int));
 if (vet){
    printf("\nPREENCHENDO OS NOVOS ITENS DO VETOR:\n");
    for(i = *tamanho; i < novo_tam; i++)
       vet[i] = ler_int();
    *tamanho = novo_tam;
    return 1;
 }
 else{
    printf("NAO FOI POSSIVEL AUMENTAR O VETOR!\n");
    return 0;
 }
}
 
int diminui_vet_int(int* vet, int* tamanho, int decremento){
 int novo_tam, i;
 
 novo_tam = *tamanho - decremento;
 if (novo_tam <= 0){
    printf("DECREMENTO INFORMADO INVALIDO!!! NAO FOI POSSIVEL DIMINUIR O VETOR!\n");
    return 0;
 }
 vet = (int *) realloc (vet, novo_tam * sizeof(int));
 if (vet){
    *tamanho = novo_tam;
    return 1;
 }
 else{
    printf("NAO FOI POSSIVEL DIMINUIR O VETOR!\n");
    return 0;
 }
}
 
int main(){
 int *vetor, tamanho, incremento, decremento;
 
 printf("Digite o tamanho do vetor:\n");
 tamanho = ler_int();
 vetor = preenche_vet_int(tamanho);
 if (vetor){
    exibe_vetor(vetor, tamanho);
    printf("Digite o quanto deseja aumentar o vetor:\n");
    incremento = ler_int();
    if (aumenta_vet_int(vetor, &tamanho, incremento))
       exibe_vetor(vetor, tamanho);
    printf("Digite o quanto deseja diminuir o vetor:\n");
    decremento = ler_int();
    if (diminui_vet_int(vetor, &tamanho, decremento))
       exibe_vetor(vetor, tamanho);
    free(vetor);
 }
 else
    printf("NAO FOI POSSIVEL ALOCAR O VETOR!\n");
 system("pause");
}


Depois do que já vimos nos post anteriores, este código ficou muito simples.
Algumas funções já são conhecidas nossas. E dentre as novas funções existe pouca coisa que ainda não foi dita.
A primeira função nova está na linha 25, e é a razão desta sequência de posts. É uma função que retorna um vetor. Um detalhe interessante dela é que damos ao usuário a liberdade de definir o tamanho deste vetor. Seu funcionamento é simples.
Declaramos um ponteiro para inteiros vet e o inicializamos com o valor 0. Este ponteiro irá armazenar o endereço do vetor que será alocado dinamicamente.
Fazemos uma chamada a função malloc com o tamanho definido pelo usuário, e atribuimos o seu retorno ao ponteiro vet.
Se a chamada teve sucesso e o vetor foi alocado, imediatamente vamos preenchê-lo.
Observe o uso do if sem nenhum operador relacional. Na verdade ele verifica o valor de vet. Por isso o inicializamos com 0. Se a função malloc falhou, o valor de vet ainda será 0 (se funcionar perfeitamente, malloc nunca retorna um endereço 0). Este if verifica se o valor de vet é diferente de 0 (cuidado com isto: por diferente entenda qualquer outro valor, inclusive negativo). Este uso do if é muito comum, e passaremos a usá-lo, sempre que possível, daqui pra frente, como pode ser visto nas linhas 84 e 88, onde ele avalia o valor retornado pelas funções que aumentam e diminuem o tamanho do vetor, respectivamente.
A segunda nova função, na linha 37, aumenta o tamanho do vetor com um valor também definido pelo usuário, usando a função realloc. Observe que usando um ponteiro para a variável tamanho, podemos alterar o seu valor dentro da própria função.
A última função nova, na linha 55, diminui o tamanho do vetor de forma semelhante à função que o aumenta, usando a função realloc com um valor definido pelo usuário. A diferença entre elas é a verificação da validade do valor informado pelo usuário, evitando um erro ao tentar alterar o tamanho do vetor para 0 ou um valor negativo.
Observe que na linha 90, já na função main, desalocamos o bloco de memória usado pelo vetor. Isto é muito importante, se não este espaço ficará 'ocupado' mesmo após o término do programa.

Com isto encerramos a nossa série sobre funções que retornam vetores e como isto é possível utilizando a alocação dinâmica.
Alguma dúvida?

sábado, 2 de janeiro de 2010

Funções x Vetores. Parte II (alocação dinâmica)

Hoje vamos ver mais a fundo os conceitos que falamos no post anterior.

Alocando memória em C
A função especifica em C para alocar memória é a malloc (memory allocation). Esta função aloca (reserva) uma área sequêncial na memória heap com a quantidade de bytes especificada, e retorna o endereço da mesma. A sua sintaxe é esta:

 malloc (tamanho);

onde tamanho é a quantidade de bytes que se deseja alocar.

No entanto, devemos lembrar, que os tipos de dados básicos do C ocupam mais de 1 byte, com a excessão do tipo char. Como não temos como precisar quantos bytes exatamente cada computador (micro, celular, etc.) utiliza para armazenar cada um destes tipos básicos, é necessário fazer uma operação chamada de conversão de tipo (type casting) explícita.

Convertendo tipos
Já vimos que a divisão de 2 inteiros, nem sempre retorna 1 outro inteiro (por exemplo: 2/3).
Para resolver este problema, a solução mais simples seria declarar as variáveis envolvidas nesta divisão como floats ao invés de inteiros. Mas há casos em que esta alteração de tipos nem sempre é possível, como é o caso da função malloc, por exemplo.
Esta função retorna, a grosso modo, o endereço de um vetor de char com a quantidade especificada. Não temos como alterar esta função, mas temos como alterar o seu retorno.
Veja no código abaixo como podemos resolver a questão da divisão usando uma conversão de tipos.


#include "stdio.h"
#include "stdlib.h"
 
int main(){
 int a = 3, b = 2;
 float c;
 
 c = (float)a/b;
 printf("%.2f\n", c);
 system("pause");
}


Observe a linha 8. Esta é a forma de converter um tipo explicitamente em C. Neste caso estamos alterando o tipo da variável a de inteiro para float, o que garante que o resultado da divisão também será um float.

Voltando à alocação dinâmica...
Agora que sabemos como é feita uma alteração de tipos em C, podemos alterar a forma de chamar a função malloc.

 (<tipo de retorno> *) malloc (tamanho * sizeof(<tipo que será armazenado>);

Duas coisas chamam a nossa atenção no comando acima.
A primeira é o * dentro dos () que fazem a alteração do tipo de retorno. Ele é necessário porque a função malloc não retorna um tipo básico, mas sim um endereço que aponta para um bloco de memória que irá armazenar variáveis deste tipo básico, ou seja, um ponteiro para este bloco.
A segunda é o uso da função sizeof dentro dos () onde é informado o tipo dos dados que serão armazenados. A função sizeof nos retorna o número de bytes necessários, no computador utilizado, para armazenar o tipo de dado especificado.
Se quisermos alocar um espaço suficiente para conter 30 inteiros e atribuir este espaço a um vetor, os comandos usados seriam:

 int* vet;
 vet = (int *) malloc (30 * sizeof(int));

E se o espaço ainda não for suficiente?
Não haveria sentido em se chamar alocação dinamica, se não pudéssemos alterar dinamicamente o tamanho do vetor. E como fazer isto?
A resposta está na função realloc (reallocation). Ela é funciona básicamente como a função malloc. A diferença é que ela altera o tamanho de um bloco já alocado pela função malloc. Observe bem isto! Não é possível alterar o tamanho de um bloco com a função realloc se antes ele não tiver sido alocado com a função malloc!
Para alterar o tamanho do vetor do comando acima, de 30 para 50 inteiros, o comando seria este:

 vet = (int *) realloc (vet, 50 * sizeof(int));

Observe que a diferença básica é que informamos qual o bloco que será alterado dentro dos () antes de informarmos o novo valor.
Um detalhe interessante, é que se já houverem valores armazenados no bloco/vetor que terá seu tamanho alterado, eles não serão perdidos, desde que o bloco tenha o seu tamanho aumentado. Se o tamanho for reduzido, serão perdidos os valores armazenados no final do bloco.

Recolhendo o lixo.
Nós vimos que as variáveis locais só existem enquanto as funções estão em execução, e que depois disso elas são eliminadas. As variáveis globais só existem enquanto o programa está em execução, e depois disto elas também são eliminadas. Já com as variáveis alocadas dinamicamente isto não ocorre.
Estas variáveis ficam na memória mesmo depois que todas funções são encerradas, inclusive a main. Isso quer dizer que elas ficam na memória mesmo depois que o programa termina. Isto causa uma perda de memória. Dependendo da quantidade de memória disponível no sistema isto pode fazer com que o sistema pare.
(Algumas linguagens de programação se encarregam de eliminar essas variáveis quando o programa se encerra. Isto é chamado de coletor de lixo (garbage colletor). Não é o caso de C/C++.)
Só existem duas formas de recuperar esta memória.
A primeira forma, e mais drástica, é reiniciando o sistema. Mas, assim que o programa for executado novamente, a memória será perdida novamente.
A segunda forma, e correta, é usando a função free.
A função free desaloca um bloco de memória alocado pela função malloc, marcando esta área como memória livre.
Para desalocar o vetor que estamos usando como exemplo, o comando seria este:

 free(vet);

No próximo e, acredito, último post desta série, veremos isto funcionando na prática. Até lá.