Obra: Programar em C++/Alocação dinâmica de memória
Nível: médio
alocação dinâmica de memória refere se ao recurso existente nas linguagens de programação onde podemos deixar de reservar memória quando programa ainda está na fase de desenvolvimento quando não sabemos ainda de quanta memória programa irá precisar para executar algum procedimento fazemos durante execução podemos alocar ou seja reservar uma quantidade de bytes para serem usados de duas maneiras diferentes em tempo de compilação ou em tempo de execução aqui tratamos do segundo caso compilador reserva espaço na memória para todos os dados declarados explicitamente mas se usarmos ponteiros precisamos colocar no ponteiro um endereço de memória existente para isto podemos usar endereço de uma variável definida previamente ou reservar espaço necessário no momento que precisemos este espaço que precisamos reservar em tempo de execução chamada de memória alocada dinamicamente reservar dinamicamente caso em que não sabemos no momento da programação quantidade de dados que deverão ser inseridos quando programa já está sendo executado em vez de tentarmos prever um limite superior para abarcar todas as situações de uso da memória temos possibilidade de reservar memória de modo dinâmico os exemplos típicos disto são os processadores de texto nos quais não sabemos quantidade de caracteres que utilizador vai escrever quando programa estiver sendo executado nestes casos podemos por exemplo receber quantidade de caracteres que usuário digita depois alocamos quantidade de memória que precisamos para guardá lo depois armazenamos para uso posterior modo mais comum de alocação de memória de alocar certa quantidade de bytes atribuí la um ponteiro provendo um array ou vetor nos tópicos seguir abordaremos estes outros casos de uso de memória alocada dinamicamente operador new existem duas maneira de alocar memória dinamicamente em tempo de execução em em podemos usar as funções de alocação dinâmica de memória existentes nas bibliotecas padrões ou usamos uma palavra chave da linguagem chamada new tratemos portanto do segundo caso pois específico da linguagem de fato esta palavra chave considerada um operador sua função reservar memória em tempo de execução do programa ela funciona de modo análogo às funções de alocação de memória da linguagem nas quais indicamos quantidade de bytes que queremos alocar as mesmas reservam referido espaço necessário porém operador new tem uma sintaxe ser seguida qual será abordada mais adiante sem usar operador new teremos um erro primário criado inicialmente pela alocação de maneira incorreta vamos ver um exemplo exemplo de alocação dinâmica de forma incorreta include using namespace std int main int numtests cout numtests int erro de declaração return de fato no exemplo acima podemos ver que numtests não tem valor definido durante compilação neste caso compilador reporta um erro normalmente exigindo um valor ou uma constante declarada como valor literal razão da exigência de ter uma constante ou literal que vamos alocar memória para array
valor literal razão da exigência de ter uma constante ou literal que vamos alocar memória para array no ato da compilação compilador necessita saber exatamente quantidade de memória que deve reservar porém se número entre colchetes uma variável compilador não sabe quanta memória deveria reservar para alocar array reformulando exemplo anterior agora com dados dinâmicos include using namespace std int main int numtests cout numtests int iptr new intnumtests colocamos um ponteiro no inicio da memória dinâmica for int iptri for int agora conseguimos criar um array onde utilizador poderá definir tamanho do array depois colocar valor para cada um dos elementos operador new retorna endereço onde começa bloco de memória que foi reservado como retorna um endereço podemos colocá lo num ponteiro assim teremos um meio de manipular conteúdo da memória alocada toda vez que mencionarmos ponteiro verificamos no exemplo uso do ponteiro que deve ser do mesmo tipo que tipo de variável que alocado dinamicamente int iptr new intnumtests temos termo new que um operador cuja função alocar dinamicamente memória temos tipo da variável alocada dinamicamente int repare que não temos nome do array uma vez que array fica sem nome para nos referirmos cada elemento do mesmo teremos de usar ponteiro podemos inicializar ponteiro de duas maneiras int idpt new int alocamos um único elemento dinâmico do tipo int idpt atribuímos valor dentro da memória dinâmica ou int idpt new int alocamos objeto int inicializamo lo com char letter new char alocamos objeto char inicializamo lo com por outro lado se quisermos criar um array de objetos char podemos proceder da forma como foi dado anteriormente int aidpt new char aloca objetos de caracteres aidpt podemos preencher os valores de cada elemento aidpt aidpt aidpt operador delete se realmente não necessitamos mais dos dados que estão num endereço de memória dinâmica devemos apagá la necessitamos liberar essa memória através do operador delete este operador entrega ao sistema operacional ou sistema de gerenciamento de memória os espaços de memória reservados dinamicamente sintaxe delete iptr operador delete não apaga ponteiro mas sim memória para onde ponteiro aponta na verdade como já dissemos anteriormente memória não de fato apagada ela retorna ao estado de disponível como memória livre para ser alocada novamente quando for necessário no caso de alocação de memória utilizando ponteiros locais ação de criar espaço através do operador new deve ser seguida de um delete depois que espaço alocado não for mais necessário da mesma forma se espaço alocado continuará ser necessário no resto do programa endereço deverá ser mantido em ponteiro global ou deverá ser retornado para ser usado depois da execução da função tempo de vida de uma variável criada dinamicamente tempo de execução do programa se fizermos um delete tivermos um ponteiro que aponta para endereço que não esteja mais alocado não conseguiremos acessar nenhuma memória específica neste
ara endereço que não esteja mais alocado não conseguiremos acessar nenhuma memória específica neste caso acessaremos uma posição de memória qualquer geralmente que está armazenada dentro do ponteiro esse valor de memória muitas vezes zero ou qualquer outro sem sentido um valor que em algum momento foi criado pelo processador em qualquer operação anteriormente executada outro problema comum se alocamos memória dinamicamente dentro de uma função usando um ponteiro local quando função termina ponteiro será destruído mas memória mantém se assim já não teríamos maneira de acessar essa memória porque nós não temos mais seu endereço além disso não teremos mais como excluí la caso não for excluída memória dinâmica alocada com operador new através do operador delete programa irá acumular memória alocada reservada que levará parada inesperada do programa por falta de memória para alocação quando outra operação new for solicitada não haver mais espaço para alocar memória temos este exemplo void myfunction int pt int av pt new int nenhum delete foi chamado int main while myfunction return quando função myfunction chamada variável av criada na pilha quando função acaba variável perdida mesmo acontece com ponteiro pt ele uma variável local ou seja quando função acaba ponteiro também termina perdido porém espaço de memória alocado dinamicamente ainda existe agora não conseguimos apagar esse espaço porque não temos mais ponteiro única maneira que tínhamos para saber onde ele estava era através do ponteiro que foi perdido quando função foi finalizada analisando programa como um todo temos medida que programa continua executar mais mais memória que será perdida no espaço de alocação dinâmica controlado pelo sistema operacional se programa continuar deixaremos de ter memória disponível programa deixará de operar nos sistemas operacionais atualmente em uso programa será interrompido uma mensagem de erro será retornada para usuário retornando um ponteiro para uma variável local analisemos código abaixo no qual há um erro include using namespace std char setname int main void char str setname ponteiros para função cout neste código podemos ver como os ponteiros mal administrados podem causar falhas que podem levar programa abortar em tempo de execução como está escrito nos comentários do código conteúdo do vetor name deixará de ser utilizável nem para operações de leitura ou principalmente em operações de escrita neste segundo caso provocará um acesso memória não autorizado programa poderá travar ou abortar solução estender tempo de vida do ponteiro do seu endereço destino uma solução possível seria tornar esse array global mas existem alternativas melhores retornando um ponteiro uma variável local estática no código seguir temos uma alternativa para uso de vetores retorno de seu conteúdo include using namespace std char setname int main void char str setname cout diferença que usamos palavra static este modificador static quando utilizado dentro de uma função para de
ença que usamos palavra static este modificador static quando utilizado dentro de uma função para declarar variáveis promove vetor categoria de permanente durante execução do programa sendo assim teremos como utilizar endereço do ponteiro str tanto dentro como fora da função setname assim evitaremos de acessar uma memória não existente retornando um ponteiro com um valor de memória criada dinamicamente outra alternativa talvez melhor include using namespace std char setname int main void char str setname cout isto funciona porque ponteiro retornado da função setname aponta para array cujo tempo de vida persiste até que usemos delete ou que programa termine valor do ponteiro local name atribuído na função main outro ponteiro str desta forma podemos manipular conteúdo da memória alocada até que não precisemos mais dele este um exemplo onde diferentes ponteiros apontam para mesmo endereço na verdade podemos atribuir qualquer ponteiro endereço alocado isso nos dá possibilidade de manipular os dados armazenados na memória dinamicamente alocada em qualquer local onde seu endereço seja conhecido para manter segurança do código sempre aconselhável que tenhamos um controle rígido sobre os ponteiros seus valores endereços armazenados imprescindível que os espaços de memória apontados por ponteiros sejam eliminados liberados quando não forem mais necessários para evitar que programa continue reservar memória não liberar levando ao esgotamento da memória ou crescimento desnecessário do uso de memória por parte do programa alocar dinamicamente arrays vetores ora que fizemos antes com variáveis podemos fazer com arrays como já introduzimos brevemente anteriormente vejamos um exemplo int pt new int aloca um array vetor de valores em int double mybills new double isso não significa que temos valor mas sim que alocamos valores em double para guardar monte de milhares de contas que recebemos mensalmente notar diferença int pt new int aloca um vetor que pode ter valores em int diferentes int pt new int aloca um único int com valor de uma variável inicializada para utilizar delete em arrays usamos ponteiro com valor da primeira célula do array delete pt delete mybills melhor maneira para alocar um array dinamicamente inicializá lo com valores usar loop int buff new int for note que segunda forma além de mais elegante não altera valor do vetor assim facilita remoção da alocação usando delete dangling pointers quando temos um ponteiro que não tem em seu conteúdo um valor de memória válido qual pertence ao programa sendo executado temos um problema grave principalmente se isso ocorreu por engano isso pode ser um problema difícil de identificar principalmente se programa já está com um certo número de linhas considerável vejamos um exemplo int mypointer mypointer new int cout neste exemplo liberamos memória dinâmica mas ponteiro continua existir isto um bug muito difícil de detectar acontece que se essa memória for acessada ou escrita será corrompida melhor maneira de e
de detectar acontece que se essa memória for acessada ou escrita será corrompida melhor maneira de evitar isso depois do delete fazer ponteiro apontar para zero fazê lo um ponteiro nulo depois disso se tentarem usar ponteiro iremos ter run time exception bug poderá ser identificado assim corrigindo código anterior ficaríamos com int mypointer mypointer new int cout verificar existência de memória para dinâmica na alocação dinâmica temos de nos certificar de que alocação no heap foi feita com sucesso podemos ver isso de duas maneiras uma são as exceções este método defaut bobby new int se isso falhar lançada uma bad_alloc exception vamos ver este caso quase no ultimo capitulo isto uma capítulo avançado nothrow aqui no caso de não se conseguir memória retorna um ponteiro nulo programa continua bobby new nothrow int supostamente este método pode ser tedioso para grandes projetos vamos ver um exemplo com caso de nothrow rememb matic include using namespace std int main int cout new nothrow inti criamos variaveis na execução if cout pn cout neste simples exemplo implementamos uma forma de alocar posições de memória para um vetor de elementos de forma que usuário possa digitar quantidade de elementos que ele vai digitar depois proceda com entrada dos valores veja que número de elementos determinado pelo próprio usuário no início do programa logo depois de indicar quantidade de números ser digitada programa pede que seja alocada quantidade de memória necessária para guardar os números serem digitados neste momento caso algum erro de alocação ocorrer sistema retorna ponteiro nulo podemos então abortar prosseguimento do programa mostrando uma mensagem de erro em seguida para que usuário fique ciente que ocorreu um erro