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 .
Monster
cospe aTreasure
paraWorld
quando morre. No seuDie()
método, adiciona o tesouro ao mundo. Ele deve usarworld->Add(new Treasure(/*...*/))
em outro para preservar o tesouro depois que ele morre. As alternativas sãoshared_ptr
(pode ser um exagero),auto_ptr
(semântica ruim para transferência de propriedade), passa por valor (desperdício) emove
+unique_ptr
(ainda não amplamente implementado).Como a pilha é mais rápida e à prova de vazamentos
No C ++, são necessárias apenas uma única instrução para alocar espaço - na pilha - para cada objeto de escopo local em uma determinada função, e é impossível vazar parte dessa memória. Esse comentário pretendia (ou deveria ter pretendido) dizer algo como "use a pilha e não a pilha".
fonte
int x; return &x;
A razão pela qual é complicada.
Primeiro, o C ++ não é coletado como lixo. Portanto, para cada novo, deve haver uma exclusão correspondente. Se você não colocar essa exclusão, há um vazamento de memória. Agora, para um caso simples como este:
Isto é simples. Mas o que acontece se "Fazer coisas" lançar uma exceção? Ops: vazamento de memória. O que acontece se problemas com "Fazer coisas"
return
cedo? Ops: vazamento de memória.E isso é para o caso mais simples . Se você devolver essa string a alguém, agora eles deverão excluí-la. E se eles passarem como argumento, a pessoa que o recebe precisa excluí-lo? Quando eles devem excluí-lo?
Ou, você pode apenas fazer o seguinte:
Não
delete
. O objeto foi criado na "pilha" e será destruído quando ficar fora do escopo. Você pode até retornar o objeto, transferindo seu conteúdo para a função de chamada. Você pode passar o objeto para funções (normalmente como uma referência ou referência constante:void SomeFunc(std::string &iCanModifyThis, const std::string &iCantModifyThis)
E assim por diante.Tudo sem
new
edelete
. Não há dúvida de quem possui a memória ou quem é responsável por excluí-la. Se você fizer:Entende-se que
otherString
possui uma cópia dos dados desomeString
. Não é um ponteiro; é um objeto separado. Eles podem ter o mesmo conteúdo, mas você pode alterar um sem afetar o outro:Veja a ideia?
fonte
main()
, existe durante o programa, não pode ser facilmente criado na pilha devido à situação, e os ponteiros para ele são passados para quaisquer funções que exijam acesso a ele , isso pode causar um vazamento no caso de uma falha no programa ou seria seguro? Eu assumiria o último, já que o SO que desaloca toda a memória do programa também deve desalocá-lo logicamente, mas não quero assumir nada quando se tratanew
.Objetos criados por
new
devem eventualmente ser usadosdelete
para que não vazem. O destruidor não será chamado, a memória não será liberada, o tempo todo. Como o C ++ não tem coleta de lixo, é um problema.Objetos criados por valor (ou seja, na pilha) morrem automaticamente quando ficam fora do escopo. A chamada de destruidor é inserida pelo compilador e a memória é liberada automaticamente ao retornar a função.
Ponteiros inteligentes como
unique_ptr
,shared_ptr
resolvem o problema de referência pendente, mas exigem disciplina de codificação e têm outros problemas em potencial (copiabilidade, loops de referência etc.).Além disso, em cenários altamente multithread,
new
há um ponto de discórdia entre threads; pode haver um impacto no desempenho por uso excessivonew
. A criação do objeto de pilha é por definição thread-local, pois cada thread tem sua própria pilha.A desvantagem dos objetos de valor é que eles morrem quando a função host retorna - você não pode passar uma referência para os que retornam ao chamador, apenas copiando, retornando ou movendo-se por valor.
fonte
new
devem eventualmente ser usadosdelete
para que não vazem". - pior ainda,new[]
deve ser correspondidodelete[]
e você terá um comportamento indefinido se você temdelete
new[]
memória oudelete[]
new
memória - muito poucos compiladores alertam sobre isso (algumas ferramentas como o Cppcheck fazem quando podem).fonte
malloc()
ou para seus amigos para alocar a memória necessária. No entanto, a pilha não pode liberar nenhum item dentro da pilha, a única maneira pela qual a memória é liberada é desenrolando da parte superior da pilha.Vejo que algumas razões importantes para fazer o mínimo possível de novidades são perdidas:
O operador
new
possui um tempo de execução não determinísticoA chamada
new
pode ou não fazer com que o sistema operacional aloque uma nova página física para o seu processo. Isso pode ser bastante lento se você fizer isso com frequência. Ou talvez já tenha um local de memória adequado pronto, não sabemos. Se o seu programa precisa ter um tempo de execução consistente e previsível (como em um sistema em tempo real ou simulação de jogo / física), você precisa evitarnew
em seus ciclos críticos de tempo.Operador
new
é uma sincronização implícita de encadeamentosSim, você me ouviu, seu sistema operacional precisa garantir que suas tabelas de páginas sejam consistentes e, como tal, a chamada
new
fará com que seu encadeamento adquira um bloqueio mutex implícito. Se você está constantemente chamando anew
partir de muitos threads, na verdade, está serializando seus threads (eu fiz isso com 32 CPUs, cada uma pressionandonew
para obter algumas centenas de bytes cada, ai! Isso foi uma pita real para depurar)O restante, como lento, fragmentação, propenso a erros, etc, já foram mencionados por outras respostas.
fonte
void * someAddress = ...; delete (T*)someAddress
mlock()
ou algo semelhante. Isso ocorre porque o sistema pode estar com pouca memória e não há páginas de memória física disponíveis para a pilha; portanto, o sistema operacional pode precisar trocar ou gravar alguns caches (limpar a memória suja) no disco antes que a execução possa prosseguir.Pré-C ++ 17:
Porque é propenso a vazamentos sutis, mesmo que você envolva o resultado em um ponteiro inteligente .
Considere um usuário "cuidadoso" que se lembre de agrupar objetos em ponteiros inteligentes:
Esse código é perigoso, porque não há garantia de que um
shared_ptr
seja construído antes de umT1
ou outroT2
. Portanto, se umnew T1()
ounew T2()
falhar após o outro for bem-sucedido, o primeiro objeto será vazado porque nãoshared_ptr
existe para destruí-lo e desalocá-lo.Solução: use
make_shared
.Pós-C ++ 17:
Isso não é mais um problema: o C ++ 17 impõe uma restrição à ordem dessas operações, neste caso, garantindo que cada chamadanew()
seja seguida imediatamente pela construção do ponteiro inteligente correspondente, sem nenhuma outra operação no meio. Isso implica que, quando o segundonew()
for chamado, é garantido que o primeiro objeto já esteja envolto em seu ponteiro inteligente, evitando assim vazamentos no caso de uma exceção ser lançada.Uma explicação mais detalhada da nova ordem de avaliação introduzida pelo C ++ 17 foi fornecida por Barry em outra resposta .Agradecemos a @Remy Lebeau por apontar que este ainda é um problema no C ++ 17 (embora menos): o
shared_ptr
construtor pode falhar ao alocar seu bloco de controle e o lançamento, caso em que o ponteiro passado para ele não é excluído.Solução: use
make_shared
.fonte
new
for bem-sucedido e ashared_ptr
construção subsequente falhar.std::make_shared()
resolveria isso tambémshared_ptr
construtor em questão aloca memória para um bloco de controle que armazena o ponteiro e o deleter compartilhado; portanto, sim, teoricamente, ele pode gerar um erro de memória. Somente os construtores de copiar, mover e aliasing não são lançados.make_shared
aloca o objeto compartilhado dentro do próprio bloco de controle, para que haja apenas 1 alocação em vez de 2.Em grande parte, é alguém que eleva suas próprias fraquezas a uma regra geral. Não há nada errado per se com a criação de objetos usando o
new
operador. O que há para argumentar é que você precisa fazer isso com alguma disciplina: se você criar um objeto, precisará garantir que ele seja destruído.A maneira mais fácil de fazer isso é criar o objeto no armazenamento automático, para que o C ++ saiba destruí-lo quando sair do escopo:
Agora, observe que quando você cai desse bloco após o fim da órbita,
foo
está fora do escopo. O C ++ chamará seu dtor automaticamente para você. Ao contrário do Java, você não precisa esperar o GC encontrá-lo.Você escreveu
você gostaria de combiná-lo explicitamente com
ou melhor ainda, aloque o seu
File *
como um "ponteiro inteligente". Se você não tomar cuidado com isso, pode levar a vazamentos.A resposta em si assume o equívoco de que, se você não usar
new
, não o alocará na pilha; de fato, em C ++ você não sabe disso. No máximo, você sabe que um pouco de memória, digamos um ponteiro, certamente está alocado na pilha. No entanto, considere se a implementação do File é algo comoem seguida,
FileImpl
vai ainda ser alocada na pilha.E sim, é melhor você ter certeza de ter
na classe também; sem ele, você vazará memória do heap, mesmo que aparentemente não tenha sido alocado no heap.
fonte
new
per se , mas se você olhar o código original ao qual o comentário se refere,new
está sendo abusado. O código é escrito como se fosse Java ou C #, ondenew
é usado para praticamente todas as variáveis, quando as coisas fazem muito mais sentido estar na pilha.new
. Ele diz que se você tiver a opção entre alocação dinâmica e armazenamento automático, use o armazenamento automático.new
, mas se você usardelete
, você está fazendo errado!new()
não deve ser usado o menos possível. Deve ser usado com o maior cuidado possível. E deve ser usado quantas vezes for necessário, ditado pelo pragmatismo.A alocação de objetos na pilha, contando com sua destruição implícita, é um modelo simples. Se o escopo necessário de um objeto se encaixa nesse modelo, não há necessidade de usar
new()
, com a associaçãodelete()
e verificação de ponteiros NULL. No caso em que você tem uma alocação muito curta de objetos na pilha, reduz os problemas de fragmentação de heap.No entanto, se a vida útil do seu objeto precisar se estender além do escopo atual,
new()
é a resposta certa. Apenas preste atenção em quando e como você chamadelete()
e as possibilidades de ponteiros NULL, usando objetos excluídos e todas as outras dicas que acompanham o uso de ponteiros.fonte
const
referência ou ponteiro ...?make_shared/_unique
é utilizável), o receptor nunca precisanew
oudelete
. Essa resposta ignora os pontos reais: (A) O C ++ fornece coisas como RVO, semântica de movimentação e parâmetros de saída - o que geralmente significa que lidar com a criação de objetos e a extensão da vida útil retornando memória alocada dinamicamente se torna desnecessário e descuidado. (B) Mesmo em situações em que a alocação dinâmica é necessária, o stdlib fornece wrappers RAII que aliviam o usuário dos detalhes internos feios.Quando você usa novo, os objetos são alocados ao heap. Geralmente é usado quando você antecipa a expansão. Quando você declara um objeto como,
é colocado na pilha.
Você sempre precisará chamar de destruir o objeto que colocou na pilha com novo. Isso abre o potencial para vazamentos de memória. Objetos colocados na pilha não são propensos a vazamento de memória!
fonte
std::string
oustd::map
, sim, um insight profundo. Minha reação inicial foi "mas também muito comumente dissociar a vida útil de um objeto do escopo do código de criação", mas realmente retornar por valor ou aceitar valores com escopo de chamador por nãoconst
referência ou ponteiro é melhor para isso, exceto quando há "expansão" envolvida também. Há alguns outros usos de som como métodos de fábrica embora ....Um motivo notável para evitar o uso excessivo da pilha é o desempenho - envolvendo especificamente o desempenho do mecanismo de gerenciamento de memória padrão usado pelo C ++. Enquanto alocação pode ser bastante rápida no caso trivial, fazendo um monte de
new
edelete
em objetos de tamanho não uniforme sem estritas leads fim não só de fragmentação de memória, mas também complica o algoritmo de alocação e pode absolutamente destruir desempenho em determinados casos.Esse é o problema que os pools de memória foram criados para resolver, permitindo mitigar as desvantagens inerentes às implementações de heap tradicionais, enquanto ainda permitem que você use o heap conforme necessário.
Melhor ainda, porém, evitar o problema completamente. Se você pode colocá-lo na pilha, faça-o.
fonte
Eu acho que o cartaz quis dizer
You do not have to allocate everything on the
heap
mais do que ostack
.Basicamente, os objetos são alocados na pilha (se o tamanho do objeto permitir, é claro) por causa do custo barato da alocação de pilha, em vez da alocação baseada em heap, que envolve bastante trabalho do alocador e adiciona verbosidade, pois é necessário gerenciar dados alocados no heap.
fonte
Costumo discordar da idéia de usar novos "demais". Embora o uso do novo original pelas classes do sistema seja um pouco ridículo. (
int *i; i = new int[9999];
? realmente?int i[9999];
é muito mais claro.) Acho que foi isso que estava deixando a cabra do comentarista.Quando você trabalha com objetos do sistema, é muito raro precisar de mais de uma referência ao mesmo objeto. Contanto que o valor seja o mesmo, isso é tudo o que importa. E os objetos do sistema normalmente não ocupam muito espaço na memória. (um byte por caractere, em uma sequência). E, se o fizerem, as bibliotecas devem ser projetadas para levar em consideração esse gerenciamento de memória (se forem bem escritas). Nesses casos, (com exceção de uma ou duas das notícias em seu código), new é praticamente inútil e serve apenas para introduzir confusões e potencial para erros.
Entretanto, quando você trabalha com suas próprias classes / objetos (por exemplo, a classe Line do pôster original), deve começar a pensar em questões como pegada de memória, persistência de dados etc. Nesse ponto, permitir várias referências ao mesmo valor é inestimável - ele permite construções como listas, dicionários e gráficos vinculados, nas quais várias variáveis precisam não apenas ter o mesmo valor, mas fazer referência exatamente ao mesmo objeto na memória. No entanto, a classe Line não possui nenhum desses requisitos. Portanto, o código do pôster original não tem absolutamente nenhuma necessidade
new
.fonte
When you're working with your own classes/objects
... você muitas vezes não tem motivos para fazê-lo! Uma pequena proporção de Qs está nos detalhes do design de contêineres por codificadores especializados. Em contraste, uma proporção deprimente é sobre a confusão de novatos que não sabem que o stdlib existe - ou recebem ativamente tarefas terríveis em cursos de 'programação', onde um tutor exige que eles reinventem a roda sem sentido - antes mesmo de aprendeu o que é uma roda e por que ela funciona. Ao promover uma alocação mais abstrata, o C ++ pode nos salvar do infinito 'segfault com lista vinculada' do C; por favor, vamos deixar .int *i; i = new int[9999];
? realmente?int i[9999];
é muito mais claro.)" Sim, é mais claro, mas para interpretar o advogado do diabo, o tipo não é necessariamente um argumento ruim. Com 9999 elementos, posso imaginar um sistema incorporado rígido que não tenha pilha suficiente para 9999 elementos: bytes de 9999x4 são ~ 40 kB, x8 ~ 80 kB. Portanto, esses sistemas podem precisar usar alocação dinâmica, supondo que eles a implementem usando memória alternativa. Ainda assim, isso poderia justificar apenas a alocação dinâmica, nãonew
; umvector
seria o verdadeiro correção nesse casostd::make_unique<int[]>()
claro).Duas razões:
delete
mais tarde, ou isso causará um vazamento de memória.fonte
new
é o novogoto
.Lembre-se do motivo de
goto
ser tão criticado: embora seja uma ferramenta poderosa e de baixo nível para controle de fluxo, as pessoas costumavam usá-lo de maneiras desnecessariamente complicadas que dificultavam o código. Além disso, os padrões mais úteis e fáceis de ler foram codificados em instruções de programação estruturada (por exemplo,for
ouwhile
); o efeito final é que o código para ondegoto
é apropriado é bastante raro; se você é tentado a escrevergoto
, provavelmente está fazendo coisas ruins (a menos que saiba realmente o que está fazendo).new
é semelhante - geralmente é usado para tornar as coisas desnecessariamente complicadas e difíceis de ler, e os padrões de uso mais úteis que podem ser codificados foram codificados em várias classes. Além disso, se você precisar usar novos padrões de uso para os quais ainda não existem classes padrão, você pode escrever suas próprias classes que as codificam!Eu diria até que isso
new
é pior do quegoto
devido à necessidade de emparelharnew
edelete
declarações.Por exemplo
goto
, se você acha que precisa usá-new
lo, provavelmente está fazendo coisas ruins - especialmente se estiver fazendo isso fora da implementação de uma classe cujo objetivo na vida é encapsular as alocações dinâmicas que você precisa fazer.fonte
O principal motivo é que os objetos na pilha são sempre difíceis de usar e gerenciar do que valores simples. Escrever código fácil de ler e manter é sempre a primeira prioridade de qualquer programador sério.
Outro cenário é a biblioteca que estamos usando fornece semântica de valores e torna desnecessária a alocação dinâmica.
Std::string
é um bom exemploNo entanto, para código orientado a objetos, é necessário usar um ponteiro - o que significa usar
new
para criá-lo antecipadamente. Para simplificar a complexidade do gerenciamento de recursos, temos dezenas de ferramentas para torná-lo o mais simples possível, como indicadores inteligentes. O paradigma baseado em objeto ou paradigma genérico assume semântica de valor e requer menos ou nãonew
, assim como os pôsteres declarados em outro lugar.Os padrões de design tradicionais, especialmente aqueles mencionados no livro GoF , usam
new
muito, pois são códigos OO típicos.fonte
For object oriented code, using a pointer [...] is a must
: absurdo . Se você está desvalorizando 'OO', referindo-se apenas a um pequeno subconjunto, o polimorfismo - também é um absurdo: as referências também funcionam.[pointer] means use new to create it beforehand
: especialmente bobagem : referências ou ponteiros podem ser usados para objetos alocados automaticamente e usados polimorficamente; me observe .[typical OO code] use new a lot
: talvez em algum livro antigo, mas quem se importa? Qualquer vagamente moderno C ++ evitanew
/ ponteiros crus sempre que possível - e é de nenhuma maneira menos OO ao fazê-loMais um ponto para todas as respostas corretas acima, depende de que tipo de programação você está fazendo. Kernel em desenvolvimento no Windows, por exemplo -> A pilha é severamente limitada e talvez você não consiga corrigir falhas de página como no modo de usuário.
Nesses ambientes, chamadas de API novas ou do tipo C são preferidas e até necessárias.
Obviamente, isso é apenas uma exceção à regra.
fonte
new
aloca objetos no heap. Caso contrário, os objetos são alocados na pilha. Procure a diferença entre os dois .fonte