Programar em C++/Alocação dinâmica de memória

De Wikibooks

Esta página precisa ser reciclada (discuta).
Ao melhorá-la, você estará ajudando o Wikilivros.

Tabela de conteúdo

[editar] Alocação dinâmica de memória

Esta memória dinâmica refere-se á possibilidade de termos o nosso programa a correr e o utilizador ter de inserir dados e como tal não sabemos exactamente a quantidade de dados é que o utilizador vai colocar, portanto temos de arranjar uma memória que nos permita lidar com esta indeterminação quanto á quantidade de dados inseridos. Este é o caso em que não sabemos no momento da programação a quantidade de dados bem que deverão ser inseridos mas o programa já está a correr. É tentar responder a perguntas: quantas pessoas existem na tua turma? Quantas letras vamos escrever, etc. Em vez de estarmos a prever um limite superior para abarcar todas as situações, temos esta possibilidade do dinâmico. Além de que colocar no momento da programação cria reserva de memória por isso, estaríamos a reservar memória para um limite que possivelmente não iríamos ter necessidade. O exemplo típico disto é os processadores de texto. em que não sabemos a quantidade de letras que o utilizador vai escrever.


Vamos voltar a uma ponta solta num dos capítulos anteriores, onde queríamos fazer com que o utilizador dissesse quantos elementos do array é que se deveria utilizar. Já dissemos antes que o declarador do nº de elementos do array tem de ser ou uma constante ou um literal, mas não pode ser uma variável. Isso dá erro. Aqui vai o exemplo desse erro:

 #include <iostream>
 using namespace std;
 int main ()
 {
   int numTests;
   cout << "Enter the number of test scores:";
   cin >> numTests;
   int testScore[numTests];
   system ("pause");
   return 0;
 }

A razão da exigência de ter um constant (ou literal) é que vamos alocar memória para o array na altura da compilação, e o compilador necessita de saber exactamente a quantidade de memória que deve reservar… porém se a variável é o size declarator, o compilador não sabe quanta memória deve reservar para alocar a variável, pois o seu valor pode mudar.

[editar] Operador new

Reformulando o exemplo anterior agora com dados dinâmicos.

 #include <iostream>
 using namespace std;
 int main ()
 {
   int numTests;
   cout << "Enter the number of test scores:";
   cin >> numTests;
   int * iPtr = new int[numTests];           //colocamos um ponteiro no inicio da memória dinâmica
   for (int i = 0; i < numTests; i++)
   {
      cout << "Enter test score #" << i + 1 << " : ";
      cin >> iPtr[i];
   }
   for (int i = 0; i < numTests; i++)
      cout << "Test score #" << i + 1 << " is "<< iPtr[i] << endl;
   delete [] iPtr;
   system ("pause");
   return 0;
 }

Ou seja conseguimos criar um array onde é o utilizador a definir o tamanho do array e que depois coloca o valor para cada um dos elementos.

O operador new retorna o endereço onde começa o bloco de memória. e como retorna um endereço vamos colocá-lo num pointer.

Necessitamos do uso do pointer que deverá ser do mesmo tipo que a tipologia de variável que é alocada dinamicamente. int * iPtr = new int[numTests];

  • Temos termo NEW. Que é um operador cuja função é alocar dinamicamente memória
  • Temos a tipologia da variável a alocar dinamicamente
  • Repare que NÃO temos o nome do array
  • Uma vez que o array fica sem nome para nos referirmos a cada elemento do array teremos de usar o pointer.

Podemos inicializar de duas maneiras:

 int *IDpt = new int;
 *IDpt = 5;

ou

 int *IDpt = new int(5);    //Allocates an int object and initializes it to value 5.
 char *letter = new char('J');

[editar] Operador Delete - Memory Leak

O tempo de vida de uma variável criada dinamicamente é o tempo de execução do programa. Se um ponteiro aponta para uma variável dinâmica e fica out of scope, já não conseguiremos aceder a essa memória criada dinamicamente. Fica indisponível. A isso se chama Memory Leak

Explicando: se alocamos memória dinamicamente dentro de uma função usando um ponteiro local, quando a função termina, o ponteiro será destruído, mas a memória mantém-se. Assim já não teríamos maneira de chamar essa memória porque ela não tem nome! Apenas tínhamos o endereço que estava no ponteiro.

Portanto, se realmente não necessitamos mais dos dados que estão nessa memória dinâmica, em vez de eles estarem a ocupar espaço vamos apagá-los! Necessitamos de libertar essa memória através do operador delete – este operador entrega ao sistema operativo a memoria reservada dinamicamente.

A sintaxe é

 delete [] iPtr;

Este delete operator não apaga o pointer mas sim a memória onde o ponteiro aponta


dynamic memory allocation funciona porque a memória não é reservada no momento da compilação, mas antes na execução. Em vez de ser no STACK (compilação) a memória é reservada no HEAP (execução). O heap é uma parte da memória que é usada como memória temporária.

Pergunta: onde é que fica situado o Heap?


Vamos explicar melhor todo este processo: pois isto tem de entrar na cabeça!!!

 void myfunction()
 {
    int *pt;
    int av;
    pt = new int(1024);
    ....
    ....
    //No delete
 } 
 int main()
 {
    while (some condition exists) // Pseudo-code
    {
        myfunction();
    }
    exit 0;
 }

quando a função “myfunction” é chamada a variável “av” é criada no stack e quando a função acaba a variavel é retirada do stack. O mesmo acontece com o ponteiro pt, ele é uma variável local. ou seja quando a função acaba o ponteiro também termina e é retirado do stack. Porém o objecto alocado dinamicamente ainda existe. E agora não conseguimos apagar esse objecto por que ele não tem nome e a única maneira que tínhamos para saber onde ele estava era através do ponteiro que terminou quando termina a função pois ele é local. então á medida que o programa continua a operar mais e mais memória será perdida do Heap (free store). se o programa continuar o tempo suficiente, deixaremos de ter memória disponível e o programa deixará de operar.

[editar] Retornando um ponteiro para uma variável local

 #include <iostream>
 using namespace std;
 char * setName();
 int main (void)
 {
   char* str = setName(); 	//ponteiros para a função
   cout << str; 			//imprimo o valor do ponteiros?
   system (“pause”);
   return 0;
 }
 char* setName (void)
 {
   char name[80]; 
   cout << "Enter your name: ";
   cin.getline (name, 80);
   return name;
 }

O que se passou aqui é que o ponteiro que sai da função setName aponta para o array local cuja vida acaba quando a função termina de executar. A solução é estender o tempo de vida. Uma solução era tornar esse array global, mas existem alternativas melhores.

[editar] Returning a Pointer to a Static Local Variable

Uma dessas alternativas é

 #include <iostream>
 using namespace std;
 char * setName();
 int main (void)
 {
   char* str = setName();
   cout << str;
   system (“pause”);
   return 0;
 }
 char* setName (void)
 {
   static char name[80]; 	//crio como static
   cout << "Enter your name: ";
   cin.getline (name, 80);
   return name;
 }

A diferença é que usamos a palavra static. O ponteiro do setName aponta para o array local, e como foi utilizado o static, ele perdura até fim da função, terminando apenas quando o programa acaba.

[editar] Returning a Pointer to a Dynamically Created Variable

Outra alternative, talvez melhor

 #include <iostream>
 using namespace std;
 char * setName();
 int main (void)
 {
  char* str= setName();
  cout << str;
  delete [] str;            //faço o delete para evitar o memory leak
  system ("pause");
  return 0;
 }
 char* setName (void)
 {
  char* name = new char[80];     //crio ponteiro chamado de name e dou o valor do endereço da memoria dinâmica
  cout << "Enter your name: ";
  cin.getline (name, 80);
  return name;
 }

Isto funciona porque o ponteiro retornado da função setname aponta para o array cujo tempo de vida persiste. O address do ponteiro local é atribuído no main a outro ponteiro no main –str. Depois este ponteiro é usado até ao fim da execução do programa. Este é um exemplo onde diferentes ponteiros apontam para o mesmo endereço.

Mas ter atenção que se fizermos o delete através de um ponteiro, ter cuidado com o segundo ponteiro que aponta para a memoria que acabou de ser deslocada.



[editar] Alocar dinamicamente Arrays

Ora o que fizemos antes com variáveis, vamos ter de fazer com arrays.

 int *pt = new int[1024];    //allocates an array of 1024 ints
 double *myBills = new double[10000];    
 /* This doesn't mean I owe 10000.0, but rather allocates an array of 10000 doubles to hold the amounts of the thousands of bills I receive monthly. */

Notar a diferença:

 int *pt = new int[1024];    //allocates an array of 1024 ints
 int *pt = new int(1024);    //allocates a single int with value 1024

a melhor maneira para alocar um array dinamicamente é usar o loop

 int *buff = new int[1024];
 for (i = 0; i < 1024; i++) 
 {
    *buff = 52; //Assigns 52 to each element;
    buff++;
 }
	ou se quisermos desta maneira
 int *buff = new int[1024];
 for (i = 0; i < 1024; i++) 
 {
    buff[i] = 52; //Assigns 52 to each element;
 }

para utilizar o delete em arrays

 delete[] pt;
 delete[] myBills;


[editar] Dangling Pointers

 int *myPointer;
 myPointer = new int(10);
 cout << "The value of myPointer is " << *myPointer << endl;
 delete myPointer;
 *myPointer = 5;
 cout << "The value of myPointer is " << *myPointer << endl;

neste exemplo libertámos a memória dinâmica, mas o ponteiro continua isto é um bug tremendo, e muito dificil de detectar. o programa continua a correr e a secção de memória pode ser usada por outro objecto dinâmico. acontece que essa memoria estará corrompida se continuar a usar o myPointer. a melhor maneira é depois do delete fazer apontar para zero, fazê-lo um ponteiro nulo. se tentarem usar o ponteiro iremos ter a run time exception e o bug pode ser identificado

assim corrigindo o código anterior ficaríamos com

 int *myPointer;
 myPointer = new int(10);
 cout << "The value of myPointer is " << *myPointer << endl;
 delete myPointer;
 myPointer = 0;
 *myPointer = 5;    //This statement will cause an run-time exception, now.
 cout << "The value of myPointer is " << *myPointer << endl;


[editar] Verificar a existência de memória para dinâmica

Na alocação dinâmica temos de nos certificar de que a alocação no heap foi feita com sucesso e podemos ver isso de duas maneiras:

  • Uma é as excepções (este é o método defaut)
 bobby = new int [5];  // if it fails an exception is thrown “bad_alloc” exception

vamos ver este caso quase no ultimo capitulo- isto é uma capítulo avançado

  • notthrow, aqui no caso de não se conseguir a memória retorna um ponteiro nulo, e o programa continua.
 bobby = new (nothrow) int [5];

supostamente este método pode ser tedioso para grandes projectos


Vamos ver um exemplo com o caso de nothrow

 // rememb-o-matic
 #include <iostream>
 using namespace std;
 int main ()
 {
  int i,n,* p;
  cout << "How many numbers would you like to type? ";
  cin >> i;
  p= new (nothrow) int[i];            //criámos I variaveis na execução
  if (p == 0)
    cout << "Error: memory could not be allocated";
  else
  {
    for (n=0; n<i; n++)
    {
      cout << "Enter number: ";
      cin >> p[n];
    }
    cout << "You have entered: ";
    for (n=0; n<i; n++)
      cout << p[n] << ", ";
    delete[] p;
  }
 system ("pause");  
 return 0;
 }



Ferramentas pessoais
Criar um livro
  • Adicionar página wiki
  • Ajuda de colecções