Aprendi C # primeiro e agora estou começando com C ++. Pelo que entendi, o operador new
em C ++ não é semelhante ao do C #.
Você pode explicar o motivo do vazamento de memória neste código de exemplo?
class A { ... };
struct B { ... };
A *object1 = new A();
B object2 = *(new B());
Respostas:
O que está acontecendo
Ao escrever,
T t;
você cria um objeto do tipoT
com duração de armazenamento automático . Ele será limpo automaticamente quando sair do escopo.Ao escrever,
new T()
você cria um objeto do tipoT
com duração de armazenamento dinâmico . Não será limpo automaticamente.Você precisa passar um ponteiro para ele
delete
para limpá-lo:No entanto, seu segundo exemplo é pior: você está desreferenciando o ponteiro e fazendo uma cópia do objeto. Dessa forma, você perde o ponteiro para o objeto criado com
new
, para nunca poder excluí-lo, mesmo que quisesse!O que você deveria fazer
Você deve preferir a duração do armazenamento automático. Precisa de um novo objeto, basta escrever:
Se você precisar de duração de armazenamento dinâmico, armazene o ponteiro no objeto alocado em um objeto de duração de armazenamento automático que o exclua automaticamente.
Este é um idioma comum que atende pelo nome de RAII não muito descritivo ( Resource Acquisition Is Initialization ). Quando você adquire um recurso que precisa de limpeza, cole-o em um objeto com duração de armazenamento automático para não precisar se preocupar em limpá-lo. Isso se aplica a qualquer recurso, seja memória, arquivos abertos, conexões de rede ou o que você desejar.
Essa
automatic_pointer
coisa já existe de várias formas, eu apenas a forneci para dar um exemplo. Uma classe muito semelhante existe na biblioteca padrão chamadastd::unique_ptr
.Há também um antigo (pré-C ++ 11) nomeado,
auto_ptr
mas agora está obsoleto porque tem um comportamento estranho de cópia.E há alguns exemplos ainda mais inteligentes, como
std::shared_ptr
, que permitem vários ponteiros para o mesmo objeto e apenas o limpam quando o último ponteiro é destruído.fonte
*p += 2
, fazer como faria com um apontador normal. Se não retornasse por referência, não imitaria o comportamento de um ponteiro normal, que é a intenção aqui.Uma explicação passo a passo:
Portanto, ao final disso, você tem um objeto no heap sem ponteiro, portanto, é impossível excluir.
A outra amostra:
é um vazamento de memória apenas se você esquecer a
delete
memória alocada:No C ++, existem objetos com armazenamento automático, aqueles criados na pilha, que são descartados automaticamente e objetos com armazenamento dinâmico, no heap, com os quais você aloca
new
e precisa se libertardelete
. (isso é tudo grosso modo)Pense que você deve ter um
delete
para cada objeto alocadonew
.EDITAR
Venha para pensar sobre isso,
object2
não precisa ser um vazamento de memória.O código a seguir é apenas para esclarecer, é uma má idéia, nunca goste de códigos como este:
Nesse caso, como
other
é passado por referência, será o objeto exato apontado pornew B()
. Portanto, obter o endereço&other
e excluir o ponteiro liberaria a memória.Mas não posso enfatizar isso o suficiente, não faça isso. É só aqui para fazer um ponto.
fonte
Dados dois "objetos":
Eles não ocuparão o mesmo local na memória. Em outras palavras,
&a != &b
Atribuir o valor de um para o outro não mudará sua localização, mas mudará seu conteúdo:
Intuitivamente, os "objetos" ponteiros funcionam da mesma maneira:
Agora, vejamos o seu exemplo:
Isso está atribuindo o valor de
new A()
aobject1
. O valor é um ponteiro, significandoobject1 == new A()
, mas&object1 != &(new A())
. (Observe que este exemplo não é um código válido, é apenas para explicação)Como o valor do ponteiro é preservado, podemos liberar a memória para a qual ele aponta:
delete object1;
Devido à nossa regra, isso se comporta da mesma formadelete (new A());
que não tem vazamento.Para o seu segundo exemplo, você está copiando o objeto apontado. O valor é o conteúdo desse objeto, não o ponteiro real. Como em qualquer outro caso
&object2 != &*(new A())
,.Perdemos o ponteiro para a memória alocada e, portanto, não podemos liberá-lo.
delete &object2;
pode parecer que funcionaria, mas porque&object2 != &*(new A())
não é equivalentedelete (new A())
e, portanto, inválido.fonte
Em C # e Java, você usa new para criar uma instância de qualquer classe e não precisa se preocupar em destruí-la posteriormente.
O C ++ também possui a palavra-chave "new", que cria um objeto, mas, diferentemente do Java ou C #, não é a única maneira de criar um objeto.
O C ++ possui dois mecanismos para criar um objeto:
Com a criação automática, você cria o objeto em um ambiente com escopo definido: - em uma função ou - como membro de uma classe (ou estrutura).
Em uma função, você a criaria da seguinte maneira:
Dentro de uma classe, você normalmente a cria dessa maneira:
No primeiro caso, os objetos são destruídos automaticamente quando o bloco de escopo é encerrado. Pode ser uma função ou um bloco de escopo dentro de uma função.
No último caso, o objeto b é destruído juntamente com a instância de A na qual ele é um membro.
Os objetos são alocados com new quando você precisa controlar a vida útil do objeto e, em seguida, ele requer exclusão para destruí-lo. Com a técnica conhecida como RAII, você cuida da exclusão do objeto no momento em que o cria, colocando-o em um objeto automático e aguarda a efetivação do destruidor desse objeto automático.
Um desses objetos é um shared_ptr que invocará uma lógica "deleter", mas somente quando todas as instâncias do shared_ptr que estão compartilhando o objeto forem destruídas.
Em geral, embora seu código possa ter muitas chamadas para novas, você deve ter chamadas limitadas para excluir e sempre certificar-se de que elas sejam chamadas de destruidores ou objetos "deletadores" inseridos em ponteiros inteligentes.
Seus destruidores também nunca devem lançar exceções.
Se você fizer isso, terá poucos vazamentos de memória.
fonte
automatic
edynamic
. Há tambémstatic
.Essa linha é a causa do vazamento. Vamos separar isso um pouco ..
O objeto2 é uma variável do tipo B, armazenada no endereço 1 (sim, estou escolhendo números arbitrários aqui). No lado direito, você solicitou um novo B ou um ponteiro para um objeto do tipo B. O programa oferece isso de bom grado e atribui seu novo B ao endereço 2 e também cria um ponteiro no endereço 3. Agora, a única maneira de acessar os dados no endereço 2 é através do ponteiro no endereço 3. Em seguida, você desferenciou o ponteiro
*
para obter os dados que o ponteiro está apontando (os dados no endereço 2). Isso efetivamente cria uma cópia desses dados e os atribui ao objeto2, atribuído no endereço 1. Lembre-se, é uma CÓPIA, não o original.Agora, aqui está o problema:
Você nunca realmente armazenou esse ponteiro em qualquer lugar em que possa usá-lo! Depois que essa tarefa é concluída, o ponteiro (memória no endereço3, que você usou para acessar o endereço2) fica fora do escopo e está além do seu alcance! Você não pode mais chamar delete e, portanto, não pode limpar a memória no endereço2. O que resta é uma cópia dos dados do endereço2 no endereço1. Duas das mesmas coisas guardadas na memória. Um que você pode acessar, o outro não (porque você perdeu o caminho). É por isso que isso é um vazamento de memória.
Eu sugiro que, a partir do seu background em C #, você leia muito sobre como os ponteiros em C ++ funcionam. Eles são um tópico avançado e podem levar algum tempo para entender, mas o uso deles será inestimável para você.
fonte
Se isso facilitar, pense na memória do computador como um hotel e os programas são clientes que contratam quartos quando precisam deles.
A maneira como esse hotel funciona é que você reserve um quarto e informe o porteiro quando sair.
Se você programar uma sala de livros e sair sem avisar o porteiro, o porteiro pensará que a sala ainda está em uso e não permitirá que mais ninguém a use. Nesse caso, há um vazamento na sala.
Se o seu programa alocar memória e não a excluir (ele simplesmente para de usá-la), o computador pensará que a memória ainda está em uso e não permitirá que mais ninguém a utilize. Este é um vazamento de memória.
Essa não é uma analogia exata, mas pode ajudar.
fonte
Ao criar,
object2
você cria uma cópia do objeto que criou com novo, mas também perde o ponteiro (nunca atribuído) (portanto, não há como excluí-lo mais tarde). Para evitar isso, você teria que fazerobject2
uma referência.fonte
Bem, você cria um vazamento de memória se, em algum momento, não liberar a memória alocada usando o
new
operador, passando um ponteiro para essa memória para odelete
operador.Nos seus dois casos acima:
Aqui você não está usando
delete
para liberar a memória; portanto, se e quando oobject1
ponteiro ficar fora do escopo, haverá um vazamento de memória, porque você o perdeu e não poderá usar odelete
operador nele.E aqui
você está descartando o ponteiro retornado por
new B()
e, portanto, nunca pode passar esse ponteiro paradelete
que a memória seja liberada. Daí outro vazamento de memória.fonte
É essa linha que está vazando imediatamente:
Aqui você está criando um novo
B
objeto na pilha e, em seguida, criando uma cópia na pilha. O que foi alocado no heap não pode mais ser acessado e, portanto, o vazamento.Esta linha não está imediatamente vazando:
Haveria um vazamento se você nunca
delete
dobject1
embora.fonte