AULA 20 - Programação 1 - Engenharia

De MediaWiki do Campus São José
Ir para: navegação, pesquisa

ALOCAÇÃO DINÂMICA DE MEMÓRIA

Objetivos

Após esta aula o aluno deverá:

  • conhecer a anatomia de um programa em execução;
  • utilizar os mecanismos básicos de alocação dinâmica de memória do C.

A área de heap e o layout de memória de um programa C

Neste link podemos ter uma ideia da anatomia de um programa na memória do computador.

http://shivacherukuri.blogspot.com.br/2011/03/memory-layout-in-cdata-segmentbss-code.html

Podemos observar que existe as seguintes áreas:

  • TEXT: área onde está o código;
  • BSS (Block Start with Symbol): dados estaticamente alocados e não inicializados;
  • DATA: dados estaticamente alocados e inicializados;
  • STACK: área de pilha (variáveis locais);
  • HEAP: área de dados alocados dinamicamente.

Quando declaramos uma variável global da forma:

 int x;

a variável x é alocada em uma área chamada BSS (dados não inicializados). Note que x possui uma área de memória reservada a ela (4 bytes) e cuja existência é o tempo de vida do programa em execução. Esta aŕea de BSS é zerada no início do programa de forma que todas as variáveis ali definidas começarão com o valor 0.

Da mesma forma, uma variável global da forma:

 int y=10;

é alocada na área de DATA. A inicialização é definida normalmente na carga do programa. Os valores de inicialização são copiados para a área de DATA na carga do programa, a partir do arquivo executável.

Por vezes, o tamanho dos dados não são conhecidos antes da execução do programa. Neste caso, pode ser interessante criá-los dinamicamente e é neste ponto que entra a área de HEAP. Trata-se de uma área de memória, gerenciada a partir de funções da biblioteca do C.

As funções mais conhecidas são (http://en.wikipedia.org/wiki/C_dynamic_memory_allocation):

http://www.linuxjournal.com/article/4681 http://www.cs.cmu.edu/~guna/15-123S11/Lectures/Lecture08.pdf

Exemplo alocação dinâmica em C
#include <stdio.h>
#include <stdlib.h> //necessário para usar as funções malloc() e free()

int main(void)
{
  float *v; //definindo o ponteiro v
  int i, num_componentes;

  printf("Informe o numero de componentes do vetor\n");
  scanf("%d", &num_componentes);

  /* ------------- Alocando dinamicamente o espaço necessário -------------

  1 - Calcular o número de bytes necessários
  primeiramente multiplicamos o número de componentes do vetor pela
  quantidade de bytes que é dada pelo comando sizeof,
  portanto temos:
  num_componentes * sizeof(float)

  2 - Reservar a quantidade de memória
  usamos malloc para reservar essa quantidade de memória,
  então temos:
  malloc(num_componentes * sizeof(float))

  3 - Converter o ponteiro para o tipo de dados desejado
  como a função malloc retorna um ponteiro do tipo void,
  precisamos converter esse ponteiro para o tipo da nossa variável, no caso float,
  por isso usamos o comando de conversão explicita:
  (float *)

  4 - juntando tudo e atribuindo em v temos o comando abaixo: */

  v = (float *) malloc(num_componentes * sizeof(float));

  //Armazenando os dados em um vetor
  for (i = 0; i < num_componentes; i++){
    printf("\nDigite o valor para a posicao %d do vetor: ", i+1);
    scanf("%f",&v[i]);
  }

  // ------ Percorrendo o vetor e imprimindo os valores ----------
  printf("\n*********** Valores do vetor dinamico ************\n\n");

  for (i = 0;i < num_componentes; i++)
  {
    printf("%.2f\n",v[i]);
  }

  //liberando o espaço de memória alocado
  free(v);

  return 0;
}

Exemplo 1: Alocação dinâmica de números inteiros (exercício puramente didático):

#include <stdlib.h>

main()
{
  int *px, *py; /*variáveis ponteiros para inteiro criadas no STACK */
  int resultado; /* variável reasultado criada no STACK */

  px = (int *) malloc(sizeof(int)); /* alocada área para um inteiro na área de HEAP */
                                    /* ponteiro aponta para esta área */ 
  *px = 5;                          /* colocado 5 na área apontada por px */

  py = (int *) malloc(sizeof(int)); /* similara anterior para ponteiro py */
  *py = 2;

  resultado = *px + *py;            /* soma os dois valores inteiros que estava na área de HEAP
                                       e coloca o valor na variável resultado */

  free (px);                        /* libera a área no HEAP que estava apontada por px */
  px = NULL;                        /* zero o ponteiro para deixá-lo apontando para área indevida */
  free (py);
  py = NULL;
}

Observe o uso do operador sizeof para captura do tamanho do inteiro. Esta abordagem garante portabilidade.


Exemplo 2 - Retornando um ponteiro para uma área alocada dentro da função.

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

/* a função retorna um ponteiro para inteiro, contendo o resultado da soma de a com b */
/* programa de caráter puramente demonstrativo */

int *soma_int(int a, int b)
{
  int *p;

  p = (int *)malloc(sizeof(int));
  *p = a+b;
  return p;  
}

main()
{
  int *meu_ptr;

  meu_ptr = soma_int(10,15);
  if (meu_ptr != NULL) {
     printf("valor de soma 10 + 15 = %d\n", *meu_ptr);
     free (meu_ptr);
     meu_ptr=NULL;
  }
}


Observe na sequência o problema que pode acontecer caso ja retorno para uma área indevida de memória.

A função retorna um ponteiro para uma área alocada na PILHA (STACK) que é uma espécie de área de rascunho para criação de variáveis locais... O C gera um warning mas gera um executável que produz resultados inesperados.

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

/* a função retorna um ponteiro para inteiro, contendo o resultado da soma de a com b */
/* programa de caráter puramente demonstrativo */

int *soma_int(int a, int b)
{
  int x; /* variável local criada no STACK */

  x = a+b;
  return &x; /* retorna um ponteiro para uma área que será liberada  após a chamada da função */
}

main()
{
  int *meu_ptr1, *meu_ptr2; 

  meu_ptr1 = soma_int(10,15);
  meu_ptr2 = soma_int(20,35);
  printf("valor de soma 10 + 15 = %d\n", *meu_ptr1);
  printf("valor de soma 20 + 35 = %d\n", *meu_ptr2);
}

Alocando uma estrutura

No exemplo seguinte é alocada uma área dinâmica para uma estrutura.

#include <stdio.h>
#include <stdlib.h>
 
void main()
{
  struct TTeste{
     int x;
     int y;
  };
  
  struct TTeste *teste;
  

  
  teste = (struct TTeste *) malloc (sizeof(struct TTeste));
  if (teste==NULL) {
      printf("erro de alocação");
      exit(1);
  }
  
  teste->x=10;
  
  free(teste);
  
  teste=NULL;

Observe que o retorno da função malloc é void (para torná-la genérica). O retorno deve ser convertido devidamente usando uma operação de casting (struct TTeste *)

Usando o typedef para ajudar na definição e declaração de estruturas

A palavra chave typedef no C permite a criação de APELIDOS para tipos (ver https://en.wikipedia.org/wiki/Typedef):

Exemplo de uso do typedef
#include<stdio.h>
#include<stdlib.h>
    //redefinindo os tipos básicos float e int
    typedef float nota;
    typedef int inteiro;
    struct tAluno
    {
    //utilizando os tipos redefinidos dentro da struct
    inteiro matricula ;
    nota prova1;
    nota prova2;
    };
    //redefinindo a struct para encurtar o comando na declaração
    typedef struct tAluno tAluno ;
    int main (void)
    {
      /* declarando a variável do tipo tAluno
      note que com a redefinição agora não é mais necessário escrever struct tAluno */
      
      tAluno aluno ;
      
      nota media = 0;
      
      printf ("Informe o numero de matricula: ");
      scanf ("%d", &aluno.matricula);
      
      printf ("Informe a nota da primeira prova: ");
      scanf ("%f", &aluno.prova1);
      
      printf ("Informe a nota da segunda prova: ");
      scanf ("%f", &aluno.prova2);
      
      media = (aluno.prova1 + aluno.prova2) / 2;
      
      printf("\nMatricula.....: %d\n", aluno.matricula);
      printf("Media do aluno: %.2f", media);

      return 0;
    }
Exemplo de ponteiro para Struct usando ->
   #include<stdio.h>
   #include<stdlib.h>
   //Usando typedef na struct para encurtar o comando na declaração
   typedef struct
   {
     int matricula ;
     float nota;
   }tAluno;
   int main (void)
   {
     //a1 é uma struct do tipo tAluno
     tAluno a1 ; 
     
     //*ptrAluno é um ponteiro do tipo tAluno que
     //recebe o endereço de a1;
     tAluno *ptrAluno = &a1;   
     
     //Atribuindo valores para os membros da struct a1
     a1.matricula =555;
     a1.nota = 8.0;
     
     //exibindo dados usando a struct diretamente usando .
     printf ("matricula: %d nota: %.2f \n",a1.matricula,a1.nota);
     
     //Podemos atribuir ou acessar um valor usando o ponteiro (*ptrAluno)
     (*ptrAluno).nota = 8.5;
     
     //exibindo dados usando um ponteiro para struct
     printf ("\nmatricula: %d nota: %.2f \n",(*ptrAluno).matricula,(*ptrAluno).nota);
     
     //ptrAluno-> substitui a notação (*ptrAluno). de forma mais intuitiva  
     
     //atribuindo um novo valor para a nota usando ptrAluno->
     ptrAluno->nota = 9.0;
     
     //portanto (*ptrAluno).nota é o mesmo que usar ptrAluno->nota
     
     printf ("\nmatricula: %d nota: %.2f \n",ptrAluno->matricula,ptrAluno->nota);
     return 0;
   }

</syntaxhighlight>

#include <stdio.h>
#include <stdlib.h>
 
void main()
{
  typedef struct {
     int x;
     int y;
  } TTeste;
  
  TTeste *teste;
  

  
  teste = (TTeste *) malloc (sizeof(TTeste));
  if (teste==NULL) {
      printf("erro de alocação");
      exit(1);
  }
  
  teste->x=10;
  
  free(teste);
  
  teste=NULL;
  
}

Note que o uso do typedef simpificou o uso da estrutura no código.

Alocando dinamicamente uma tabela de estruturas

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

void main()
{
  struct TTeste{
     int x;
     int y;
  } *teste;

  if ((teste = (struct TTeste *) malloc (100*sizeof(struct TTeste)))==NULL) {
      printf("erro de alocação");
      exit(1);
  }



  teste[10].x= 5;

  if ((teste = realloc(teste, 10000*sizeof(struct TTeste)))==NULL) {
      printf("erro de alocação");
      exit(1);
  }

  teste[9000].x=20;
  free(teste);

}

Exercícios:

  1. Criar uma função que recebe dois números complexos como parâmetro e retorna um ponteiro para uma área dinamicamente criada, contendo a soma destes números. A função retorna NULL caso não tenha sido possível alocar a área para a soma.
    solução Ex. 1
    #include <stdlib.h>
    #include <stdio.h>
    
    struct tipo_complexo {
       float real;
       float imag;
    };
    
    
    struct tipo_complexo *soma_complexo(struct tipo_complexo a, struct tipo_complexo b)
    {
      struct tipo_complexo *p;
    
      p = (struct tipo_complexo*) malloc(sizeof(struct tipo_complexo));
    
      if (p != NULL) {
          p->real = a.real + b.real;
          p->imag = a.imag + b.imag;
      }
    
      return p;
    }
    
    int main()
    {
      struct tipo_complexo alfa = {2.5, 3.6}, beta = {2.9, 8.7}, *p_result;
     
      p_result = soma_complexo(alfa, beta);
    
      printf("%f %f\n", p_result->real, p_result->imag);
      free(p_result);
      p_result = NULL;
    
      return (0);
    }
    
  2. Criar uma função que recebe uma string passada como parâmetro e retorna um ponteiro para uma string (armazenada em uma área dinâmicamente criada) contendo uma cópia da string passada mas com caracteres capitalizados.
    solução Ex. 2
    //Autor: Victor C
    
    #include <stdlib.h>
    #include <stdio.h>
    
    
    
    
    // depois criar um parâmetro na função
    char *func(char *string) {
      char *p = (char *) malloc(50 * sizeof(char)); // 50 porque sao 50 espaços no vetor
    // que seria a "mesma" coisa que : string[50]
      p=string;  
      return p;
     }
     
    
    int main()
    {
      char *p;
      char string[50] = "IFSC-SJ";
      p = func(string);
      printf("%s",p);
    }