Introdução
Neste laboratório, você aprenderá como lidar com a alocação dinâmica de memória em C++. Começará alocando memória usando o operador new, e em seguida liberará a memória com o operador delete. Você também explorará a criação de arrays dinâmicos, o uso de smart pointers como shared_ptr e unique_ptr, e a verificação de vazamentos de memória (memory leaks). Essas habilidades são essenciais para o gerenciamento de memória eficaz na programação C++.
O laboratório cobre as seguintes etapas principais: alocação de memória usando o operador new, liberação de memória com o operador delete, criação de arrays dinâmicos com new[], exclusão de arrays usando delete[], implementação do smart pointer shared_ptr, uso de unique_ptr para propriedade exclusiva, verificação de vazamentos de memória e tratamento de falhas na alocação de memória.
Alocar Memória Usando o Operador new
Nesta etapa, você aprenderá como usar o operador new em C++ para alocar memória dinamicamente em tempo de execução. A alocação dinâmica de memória permite que você crie variáveis e arrays cujo tamanho é determinado durante a execução do programa, proporcionando mais flexibilidade em comparação com a alocação de memória estática.
Abra o WebIDE e crie um novo arquivo chamado dynamic_memory.cpp no diretório ~/project:
touch ~/project/dynamic_memory.cpp
Adicione o seguinte código a dynamic_memory.cpp:
#include <iostream>
int main() {
// Dynamically allocate an integer using new
int* dynamicInteger = new int;
// Assign a value to the dynamically allocated integer
*dynamicInteger = 42;
// Print the value of the dynamically allocated integer
std::cout << "Dynamically allocated integer value: " << *dynamicInteger << std::endl;
// Print the memory address of the dynamically allocated integer
std::cout << "Memory address: " << dynamicInteger << std::endl;
return 0;
}
Vamos detalhar os conceitos-chave:
int* dynamicInteger = new int;:- O operador
newaloca memória para um inteiro - Retorna um ponteiro para a memória alocada
- Cria a variável na heap (pilha de memória dinâmica), não na stack (pilha de memória estática)
- O operador
*dynamicInteger = 42;:- Usa o operador de desreferência
*para atribuir um valor - Armazena o valor 42 na memória alocada dinamicamente
- Usa o operador de desreferência
Compile e execute o programa:
g++ dynamic_memory.cpp -o dynamic_memory
./dynamic_memory
Exemplo de saída:
Dynamically allocated integer value: 42
Memory address: 0x55f4e8a042a0
Pontos-chave sobre o operador new:
- Aloca memória dinamicamente durante o tempo de execução
- Retorna um ponteiro para a memória alocada
- A memória é alocada na heap
- O tamanho pode ser determinado em tempo de execução
- Permite um gerenciamento de memória mais flexível
Observação: Nas próximas etapas, você aprenderá como liberar corretamente essa memória alocada dinamicamente para evitar vazamentos de memória (memory leaks).
Liberar Memória com o Operador delete
Nesta etapa, você aprenderá como usar o operador delete para liberar corretamente a memória alocada dinamicamente em C++. Não liberar a memória pode levar a vazamentos de memória (memory leaks), o que pode causar problemas de desempenho em seu programa.
Abra o WebIDE e crie um novo arquivo chamado memory_release.cpp no diretório ~/project:
touch ~/project/memory_release.cpp
Adicione o seguinte código a memory_release.cpp:
#include <iostream>
int main() {
// Dynamically allocate an integer
int* dynamicInteger = new int;
// Assign a value to the dynamically allocated integer
*dynamicInteger = 42;
// Print the value before releasing memory
std::cout << "Value before release: " << *dynamicInteger << std::endl;
// Release the dynamically allocated memory using delete
delete dynamicInteger;
// Set the pointer to nullptr after deletion
dynamicInteger = nullptr;
// Demonstrate safe pointer usage
if (dynamicInteger == nullptr) {
std::cout << "Memory has been successfully released" << std::endl;
}
return 0;
}
Pontos-chave sobre o operador delete:
delete dynamicInteger;:- Libera a memória previamente alocada com
new - Previne vazamentos de memória
- Devolve a memória ao sistema
- Libera a memória previamente alocada com
dynamicInteger = nullptr;:- Define o ponteiro como
nullptrapós a exclusão - Ajuda a prevenir o uso acidental da memória liberada
- Fornece uma verificação de segurança adicional
- Define o ponteiro como
Compile e execute o programa:
g++ memory_release.cpp -o memory_release
./memory_release
Exemplo de saída:
Value before release: 42
Memory has been successfully released
Regras importantes de gerenciamento de memória:
- Sempre use
deletepara memória alocada comnew - Defina os ponteiros como
nullptrapós a exclusão - Nunca use um ponteiro após ter sido excluído
- Cada
newdeve ter umdeletecorrespondente
Criar Arrays Dinâmicos com new[]
Nesta etapa, você aprenderá como criar arrays dinâmicos usando o operador new[] em C++. Arrays dinâmicos permitem que você aloque memória para múltiplos elementos em tempo de execução, proporcionando mais flexibilidade do que arrays estáticos.
Abra o WebIDE e crie um novo arquivo chamado dynamic_array.cpp no diretório ~/project:
touch ~/project/dynamic_array.cpp
Adicione o seguinte código a dynamic_array.cpp:
#include <iostream>
int main() {
// Declare the size of the dynamic array
int arraySize = 5;
// Create a dynamic integer array using new[]
int* dynamicArray = new int[arraySize];
// Initialize array elements
for (int i = 0; i < arraySize; ++i) {
dynamicArray[i] = i * 10;
}
// Print array elements
std::cout << "Dynamic Array Contents:" << std::endl;
for (int i = 0; i < arraySize; ++i) {
std::cout << "Element " << i << ": " << dynamicArray[i] << std::endl;
}
return 0;
}
Pontos-chave sobre arrays dinâmicos:
int* dynamicArray = new int[arraySize];:- Aloca memória para um array de inteiros
arraySizedetermina o número de elementos- A memória é alocada na heap
- O tamanho pode ser determinado em tempo de execução
Inicialização e acesso ao array:
- Use a indexação de array padrão
dynamicArray[i] - Pode ser manipulado como um array regular
- Permite dimensionamento dinâmico com base nas condições de tempo de execução
- Use a indexação de array padrão
Compile e execute o programa:
g++ dynamic_array.cpp -o dynamic_array
./dynamic_array
Exemplo de saída:
Dynamic Array Contents:
Element 0: 0
Element 1: 10
Element 2: 20
Element 3: 30
Element 4: 40
Considerações importantes:
- Arrays dinâmicos fornecem flexibilidade na alocação de memória
- O tamanho pode ser determinado em tempo de execução
- Lembre-se de usar
new[]para alocação edelete[]para desalocação - Sempre combine os métodos de alocação e desalocação
Excluir Arrays Usando delete[]
Nesta etapa, você aprenderá como liberar corretamente a memória alocada para arrays dinâmicos usando o operador delete[]. O gerenciamento adequado da memória é crucial para evitar vazamentos de memória (memory leaks) e garantir o uso eficiente dos recursos.
Abra o WebIDE e crie um novo arquivo chamado delete_array.cpp no diretório ~/project:
touch ~/project/delete_array.cpp
Adicione o seguinte código a delete_array.cpp:
#include <iostream>
int main() {
// Declare the size of the dynamic array
int arraySize = 5;
// Create a dynamic integer array using new[]
int* dynamicArray = new int[arraySize];
// Initialize array elements
for (int i = 0; i < arraySize; ++i) {
dynamicArray[i] = i * 10;
}
// Print array elements before deletion
std::cout << "Array Contents Before Deletion:" << std::endl;
for (int i = 0; i < arraySize; ++i) {
std::cout << "Element " << i << ": " << dynamicArray[i] << std::endl;
}
// Release the dynamically allocated array using delete[]
delete[] dynamicArray;
// Set the pointer to nullptr after deletion
dynamicArray = nullptr;
// Demonstrate safe pointer usage
if (dynamicArray == nullptr) {
std::cout << "Dynamic array has been successfully deleted" << std::endl;
}
return 0;
}
Pontos-chave sobre a exclusão de arrays dinâmicos:
delete[] dynamicArray;:- Libera corretamente a memória para todo o array
- Deve usar
delete[]para arrays alocados comnew[] - Previne vazamentos de memória
- Chama o destrutor para cada elemento do array
dynamicArray = nullptr;:- Define o ponteiro como
nullptrapós a exclusão - Impede o acesso acidental à memória liberada
- Fornece uma verificação de segurança adicional
- Define o ponteiro como
Compile e execute o programa:
g++ delete_array.cpp -o delete_array
./delete_array
Exemplo de saída:
Array Contents Before Deletion:
Element 0: 0
Element 1: 10
Element 2: 20
Element 3: 30
Element 4: 40
Dynamic array has been successfully deleted
Regras importantes de gerenciamento de memória:
- Sempre use
delete[]para arrays alocados comnew[] - Nunca misture
deleteedelete[] - Defina os ponteiros como
nullptrapós a exclusão - Cada
new[]deve ter umdelete[]correspondente
Implementar Smart Pointer shared_ptr
Nesta etapa, você aprenderá sobre shared_ptr, um smart pointer da Biblioteca Padrão C++ que fornece gerenciamento automático de memória e contagem de referência. Smart pointers ajudam a prevenir vazamentos de memória e simplificam o gerenciamento de memória.
Abra o WebIDE e crie um novo arquivo chamado shared_pointer.cpp no diretório ~/project:
touch ~/project/shared_pointer.cpp
Adicione o seguinte código a shared_pointer.cpp:
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass(int value) : data(value) {
std::cout << "Constructor called. Value: " << data << std::endl;
}
~MyClass() {
std::cout << "Destructor called. Value: " << data << std::endl;
}
int getData() const { return data; }
private:
int data;
};
int main() {
// Create a shared_ptr to MyClass
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>(42);
// Create another shared_ptr pointing to the same object
std::shared_ptr<MyClass> ptr2 = ptr1;
// Print reference count
std::cout << "Reference Count: " << ptr1.use_count() << std::endl;
// Access object through shared_ptr
std::cout << "Data from ptr1: " << ptr1->getData() << std::endl;
std::cout << "Data from ptr2: " << ptr2->getData() << std::endl;
// Pointers will automatically free memory when they go out of scope
return 0;
}
Pontos-chave sobre shared_ptr:
std::make_shared<MyClass>(42):- Cria um ponteiro compartilhado com alocação dinâmica de memória
- Inicializa o objeto com o argumento do construtor
- Mais eficiente do que a criação separada de
neweshared_ptr
ptr1.use_count():- Retorna o número de instâncias
shared_ptrapontando para o mesmo objeto - Ajuda a rastrear a contagem de referência do objeto
- Retorna o número de instâncias
Gerenciamento automático de memória:
- A memória é liberada automaticamente quando nenhum
shared_ptrreferencia o objeto - Previne erros de gerenciamento manual de memória
- A memória é liberada automaticamente quando nenhum
Compile e execute o programa:
g++ -std=c++11 shared_pointer.cpp -o shared_pointer
./shared_pointer
Exemplo de saída:
Constructor called. Value: 42
Reference Count: 2
Data from ptr1: 42
Data from ptr2: 42
Destructor called. Value: 42
Características importantes de shared_ptr:
- Gerenciamento automático de memória
- Contagem de referência
- Previne vazamentos de memória
- Contagem de referência thread-safe
Usar unique_ptr para Propriedade Exclusiva
Nesta etapa, você aprenderá sobre unique_ptr, um smart pointer que fornece propriedade exclusiva de recursos alocados dinamicamente. Ao contrário de shared_ptr, unique_ptr garante que apenas um ponteiro pode possuir o recurso por vez.
Abra o WebIDE e crie um novo arquivo chamado unique_pointer.cpp no diretório ~/project:
touch ~/project/unique_pointer.cpp
Adicione o seguinte código a unique_pointer.cpp:
#include <iostream>
#include <memory>
class Resource {
public:
Resource(int value) : data(value) {
std::cout << "Resource created. Value: " << data << std::endl;
}
~Resource() {
std::cout << "Resource destroyed. Value: " << data << std::endl;
}
int getData() const { return data; }
private:
int data;
};
void processResource(std::unique_ptr<Resource> resource) {
std::cout << "Processing resource with value: " << resource->getData() << std::endl;
// Resource will be automatically deleted when function ends
}
int main() {
// Create a unique_ptr
std::unique_ptr<Resource> ptr1 = std::make_unique<Resource>(42);
// Print resource data
std::cout << "Resource value: " << ptr1->getData() << std::endl;
// Transfer ownership using std::move
std::unique_ptr<Resource> ptr2 = std::move(ptr1);
// ptr1 is now nullptr
if (ptr1 == nullptr) {
std::cout << "ptr1 is now null after moving ownership" << std::endl;
}
// Pass unique_ptr to a function (ownership is transferred)
processResource(std::move(ptr2));
return 0;
}
Pontos-chave sobre unique_ptr:
std::make_unique<Resource>(42):- Cria um ponteiro único com alocação dinâmica de memória
- Garante a propriedade exclusiva do recurso
std::move(ptr1):- Transfere a propriedade do recurso
- O ponteiro original se torna
nullptr - Impede que vários ponteiros possuam o mesmo recurso
Gerenciamento automático de memória:
- O recurso é automaticamente excluído quando o unique_ptr sai do escopo
- Nenhum gerenciamento manual de memória é necessário
Compile e execute o programa:
g++ -std=c++14 unique_pointer.cpp -o unique_pointer
./unique_pointer
Exemplo de saída:
Resource created. Value: 42
Resource value: 42
ptr1 is now null after moving ownership
Processing resource with value: 42
Resource destroyed. Value: 42
Características importantes de unique_ptr:
- Propriedade exclusiva
- Gerenciamento automático de memória
- Não pode ser copiado, apenas movido
- Previne vazamentos de recursos
Verificar Vazamentos de Memória
Nesta etapa, você aprenderá como detectar e prevenir vazamentos de memória em C++ usando o Valgrind, uma poderosa ferramenta de depuração de memória. Vazamentos de memória ocorrem quando a memória alocada dinamicamente não é liberada corretamente, fazendo com que seu programa consuma quantidades crescentes de memória.
Primeiro, instale o Valgrind no terminal:
sudo apt update
sudo apt install -y valgrind
Crie um exemplo de vazamento de memória no WebIDE criando memory_leak.cpp no diretório ~/project:
touch ~/project/memory_leak.cpp
Adicione o seguinte código a memory_leak.cpp:
#include <iostream>
class Resource {
public:
Resource(int value) : data(value) {
std::cout << "Resource created: " << data << std::endl;
}
~Resource() {
std::cout << "Resource destroyed: " << data << std::endl;
}
private:
int data;
};
void createMemoryLeak() {
// This creates a memory leak because the resource is not deleted
Resource* leak = new Resource(42);
// Missing: delete leak;
}
int main() {
// Simulate multiple memory leaks
for (int i = 0; i < 3; ++i) {
createMemoryLeak();
}
return 0;
}
Compile o programa com símbolos de depuração:
g++ -g memory_leak.cpp -o memory_leak
Execute o Valgrind para detectar vazamentos de memória:
valgrind --leak-check=full ./memory_leak
Exemplo de saída do Valgrind:
==1988== Memcheck, a memory error detector
==1988== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==1988== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==1988== Command: ./memory_leak
==1988==
Resource created: 42
Resource created: 42
Resource created: 42
==1988==
==1988== HEAP SUMMARY:
==1988== in use at exit: 12 bytes in 3 blocks
==1988== total heap usage: 5 allocs, 2 frees, 73,740 bytes allocated
==1988==
==1988== 12 bytes in 3 blocks are definitely lost in loss record 1 of 1
==1988== at 0x4849013: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==1988== by 0x109241: createMemoryLeak() (memory_leak.cpp:19)
==1988== by 0x109299: main (memory_leak.cpp:26)
==1988==
==1988== LEAK SUMMARY:
==1988== definitely lost: 12 bytes in 3 blocks
==1988== indirectly lost: 0 bytes in 0 blocks
==1988== possibly lost: 0 bytes in 0 blocks
==1988== still reachable: 0 bytes in 0 blocks
==1988== suppressed: 0 bytes in 0 blocks
==1988==
==1988== For lists of detected and suppressed errors, rerun with: -s
==1988== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
Como você pode ver, o Valgrind detectou um vazamento de memória de 12 bytes em 3 blocos. O objeto Resource criado em createMemoryLeak() não foi excluído, causando o vazamento de memória.
Para corrigir o vazamento de memória, modifique o código para excluir corretamente os recursos:
void createMemoryLeak() {
// Properly delete the dynamically allocated resource
Resource* leak = new Resource(42);
delete leak; // Add this line to prevent memory leak
}
Compile e execute o programa novamente com o Valgrind para verificar se o vazamento de memória foi corrigido:
g++ -g memory_leak.cpp -o memory_leak
valgrind --leak-check=full ./memory_leak
==2347== Memcheck, a memory error detector
==2347== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==2347== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==2347== Command: ./memory_leak
==2347==
Resource created: 42
Resource destroyed: 42
Resource created: 42
Resource destroyed: 42
Resource created: 42
Resource destroyed: 42
==2347==
==2347== HEAP SUMMARY:
==2347== in use at exit: 0 bytes in 0 blocks
==2347== total heap usage: 5 allocs, 5 frees, 73,740 bytes allocated
==2347==
==2347== All heap blocks were freed -- no leaks are possible
==2347==
==2347== For lists of detected and suppressed errors, rerun with: -s
==2347== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Pontos-chave sobre vazamentos de memória:
- Sempre
deletea memória alocada dinamicamente - Use smart pointers como
unique_ptreshared_ptr - Use o Valgrind para detectar vazamentos de memória
- Compile com a flag
-gpara obter melhores informações de depuração
Lidar com Falhas na Alocação de Memória
Nesta etapa, você aprenderá como lidar com falhas de alocação de memória em C++. Quando a alocação dinâmica de memória falha, o operador new lança uma exceção std::bad_alloc, que você pode capturar e tratar de forma adequada.
Abra o WebIDE e crie um novo arquivo chamado memory_allocation.cpp no diretório ~/project:
touch ~/project/memory_allocation.cpp
Adicione o seguinte código a memory_allocation.cpp:
#include <iostream>
#include <new>
#include <limits>
void demonstrateMemoryAllocation() {
try {
// Attempt to allocate a large amount of memory
const size_t largeSize = 1000000000000; // 1 trillion integers
int* largeArray = new int[largeSize];
// This line will not be reached if allocation fails
std::cout << "Memory allocation successful" << std::endl;
// Clean up allocated memory
delete[] largeArray;
}
catch (const std::bad_alloc& e) {
// Handle memory allocation failure
std::cerr << "Memory allocation failed: " << e.what() << std::endl;
}
}
void safeMemoryAllocation() {
// Use std::nothrow to prevent exception throwing
int* safeArray = new(std::nothrow) int[1000000];
if (safeArray == nullptr) {
std::cerr << "Memory allocation failed silently" << std::endl;
return;
}
// Use the allocated memory
std::cout << "Safe memory allocation successful" << std::endl;
// Clean up allocated memory
delete[] safeArray;
}
int main() {
std::cout << "Demonstrating memory allocation failure handling:" << std::endl;
// Method 1: Using exception handling
demonstrateMemoryAllocation();
// Method 2: Using std::nothrow
safeMemoryAllocation();
return 0;
}
Pontos-chave sobre o tratamento de falhas de alocação de memória:
Tratamento de exceção com
std::bad_alloc:- Bloco
try-catchcaptura exceções de alocação de memória - Fornece informações detalhadas sobre o erro
- Evita a falha do programa
- Bloco
Alocação
std::nothrow:- Impede o lançamento de exceções
- Retorna
nullptrem caso de falha na alocação - Permite o tratamento silencioso de falhas
Compile e execute o programa:
g++ memory_allocation.cpp -o memory_allocation
./memory_allocation
Exemplo de saída:
Demonstrating memory allocation failure handling:
Memory allocation failed: std::bad_alloc
Safe memory allocation successful
Estratégias importantes de alocação de memória:
- Sempre verifique se há falhas na alocação
- Use tratamento de exceção ou
std::nothrow - Implemente mecanismos de fallback
- Evite alocar blocos de memória extremamente grandes
Resumo
Neste laboratório, você aprenderá como lidar com a alocação dinâmica de memória em C++. Primeiro, você aprenderá a usar o operador new para alocar memória dinamicamente em tempo de execução, permitindo um gerenciamento de memória mais flexível em comparação com a alocação estática. Em seguida, você aprenderá como liberar corretamente essa memória alocada dinamicamente usando o operador delete para evitar vazamentos de memória. Além disso, você explorará a criação de arrays dinâmicos com new[] e sua exclusão com delete[]. O laboratório também aborda o uso de smart pointers, como shared_ptr e unique_ptr, para simplificar o gerenciamento de memória e evitar armadilhas comuns. Por fim, você aprenderá técnicas para verificar e lidar com falhas de alocação de memória.



