Existem duas técnicas de alocação de memória amplamente usadas: alocação automática e alocação dinâmica. Geralmente, há uma região correspondente da memória para cada um: a pilha e a pilha.
Pilha
A pilha sempre aloca memória de maneira seqüencial. Isso pode ser feito porque exige que você libere a memória na ordem inversa (primeiro a entrar, último a sair: FILO). Esta é a técnica de alocação de memória para variáveis locais em muitas linguagens de programação. É muito, muito rápido, porque requer contabilidade mínima e o próximo endereço a ser alocado está implícito.
No C ++, isso é chamado de armazenamento automático porque o armazenamento é reivindicado automaticamente no final do escopo. Assim que a execução do bloco de código atual (usando delimitado {}
) for concluída, a memória de todas as variáveis desse bloco será coletada automaticamente. Este também é o momento em que os destruidores são chamados para limpar recursos.
Montão
A pilha permite um modo de alocação de memória mais flexível. A contabilidade é mais complexa e a alocação é mais lenta. Como não há ponto de liberação implícito, você deve liberar a memória manualmente, usando delete
ou delete[]
( free
em C). No entanto, a ausência de um ponto de liberação implícito é a chave para a flexibilidade do heap.
Razões para usar a alocação dinâmica
Mesmo que o uso do heap seja mais lento e potencialmente leve a vazamentos ou fragmentação da memória, há casos de uso perfeitamente bons para alocação dinâmica, pois é menos limitado.
Dois motivos principais para usar a alocação dinâmica:
Você não sabe quanta memória precisa em tempo de compilação. Por exemplo, ao ler um arquivo de texto em uma string, você geralmente não sabe qual o tamanho do arquivo, portanto, não pode decidir quanta memória alocar até executar o programa.
Você deseja alocar memória que persistirá após sair do bloco atual. Por exemplo, você pode escrever uma função string readfile(string path)
que retorne o conteúdo de um arquivo. Nesse caso, mesmo que a pilha possa conter todo o conteúdo do arquivo, você não poderá retornar de uma função e manter o bloco de memória alocado.
Por que a alocação dinâmica geralmente é desnecessária
No C ++, há uma construção interessante chamada destruidor . Esse mecanismo permite gerenciar recursos alinhando a vida útil do recurso com a vida útil de uma variável. Essa técnica é chamada RAII e é o ponto distintivo do C ++. Ele "agrupa" recursos em objetos. std::string
é um exemplo perfeito. Este trecho:
int main ( int argc, char* argv[] )
{
std::string program(argv[0]);
}
na verdade aloca uma quantidade variável de memória. O std::string
objeto aloca memória usando a pilha e a libera em seu destruidor. Nesse caso, você não precisou gerenciar nenhum recurso manualmente e ainda obteve os benefícios da alocação dinâmica de memória.
Em particular, isso implica que neste trecho:
int main ( int argc, char* argv[] )
{
std::string * program = new std::string(argv[0]); // Bad!
delete program;
}
há alocação de memória dinâmica desnecessária. O programa requer mais digitação (!) E apresenta o risco de esquecer de desalocar a memória. Faz isso sem nenhum benefício aparente.
Por que você deve usar o armazenamento automático o mais rápido possível
Basicamente, o último parágrafo resume. Usar o armazenamento automático o mais rápido possível cria seus programas:
- mais rápido para digitar;
- mais rápido quando executado;
- menos propenso a vazamentos de memória / recursos.
Pontos bônus
Na pergunta referenciada, existem preocupações adicionais. Em particular, a seguinte classe:
class Line {
public:
Line();
~Line();
std::string* mString;
};
Line::Line() {
mString = new std::string("foo_bar");
}
Line::~Line() {
delete mString;
}
Na verdade, é muito mais arriscado do que o seguinte:
class Line {
public:
Line();
std::string mString;
};
Line::Line() {
mString = "foo_bar";
// note: there is a cleaner way to write this.
}
O motivo é que std::string
define corretamente um construtor de cópias. Considere o seguinte programa:
int main ()
{
Line l1;
Line l2 = l1;
}
Usando a versão original, este programa provavelmente trava, pois ele usa delete
a mesma string duas vezes. Usando a versão modificada, cada Line
instância terá sua própria instância de cadeia , cada uma com sua própria memória e ambas serão liberadas no final do programa.
Outras notas
O uso extensivo de RAII é considerado uma prática recomendada em C ++ por todos os motivos acima. No entanto, há um benefício adicional que não é imediatamente óbvio. Basicamente, é melhor que a soma de suas partes. Todo o mecanismo é composto . Escala.
Se você usar a Line
classe como um bloco de construção:
class Table
{
Line borders[4];
};
Então
int main ()
{
Table table;
}
aloca quatro std::string
instâncias, quatro Line
instâncias, uma Table
instância e todo o conteúdo da string e tudo é liberado automaticamente .