Programar em C++/Alocação dinâmica de memória
De Wikibooks
[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; }

