Excluindo um ponteiro em C ++

91

Contexto: Estou tentando entender os ponteiros, acabamos de vê-los há algumas semanas na escola e enquanto praticava hoje encontrei um idiota? problema, pode ser super simples para você, mas tenho pouca ou nenhuma experiência em programação.

Já vi algumas perguntas no SO sobre a exclusão de ponteiros, mas todas parecem estar relacionadas à exclusão de uma classe e não a um ponteiro "simples" (ou qualquer que seja o termo adequado), aqui está o código que estou tentando corre:

#include <iostream>;

using namespace std;

int main() {
  int myVar,
      *myPointer;

  myVar = 8;
  myPointer = &myVar;

  cout << "delete-ing pointers " << endl;
  cout << "Memory address: " << myPointer << endl;

  // Seems I can't *just* delete it, as it triggers an error 
  delete myPointer;
  cout << "myPointer: " << myPointer << endl;
  // Error: a.out(14399) malloc: *** error for object 0x7fff61e537f4:
  // pointer being freed was not allocated
  // *** set a breakpoint in malloc_error_break to debug
  // Abort trap: 6

  // Using the new keyword befor deleting it works, but
  // does it really frees up the space? 
  myPointer = new int;
  delete myPointer;
  cout << "myPointer: " << myPointer << endl;
  // myPointer continues to store a memory address.

  // Using NULL before deleting it, seems to work. 
  myPointer = NULL;
  delete myPointer;
  cout << "myPointer: " << myPointer << endl;
  // myPointer returns 0.

}

Então, minhas perguntas são:

  1. Por que o primeiro caso não funciona? Parece o uso mais simples de usar e excluir um ponteiro? O erro diz que a memória não foi alocada, mas 'cout' retornou um endereço.
  2. No segundo exemplo o erro não está sendo disparado mas fazer um cout do valor de myPointer ainda retorna um endereço de memória?
  3. # 3 realmente funciona? Parece que funciona, o ponteiro não está mais armazenando um endereço. Essa é a maneira correta de excluir um ponteiro?

Desculpe pela longa pergunta, queria deixar isso o mais claro possível, também para reiterar, eu tenho pouca experiência em programação, então se alguém pudesse responder usando termos leigos, ficaria muito grato!

leópico
fonte
16
O motivo de você não ver o primeiro exemplo é porque ele está errado. Só o deleteque você new. Também não é necessário que o ponteiro se defina como NULL após você excluí-lo. Se você quiser segurança aí, use smart pointers, que liberam a memória para você e dão erros quando você tenta acessá-los quando não estão segurando algo.
Chris
Hmm, ok, não sei o que são dicas inteligentes, mas vou dar uma olhada, obrigado!
leopic
1
Em suma, eles fazem o que descrevi. Para reter algo novo, você liga resete libera o antigo. Para liberar sem reposição, você liga release. Quando sai do escopo, é destruído e pode liberar a memória com base no tipo que é. std::unique_ptrdestina-se a apenas um proprietário. std::shared_ptrlibera-o quando o último proprietário deixa de possuir o recurso. Eles também são seguros contra exceções. Se você alocar um recurso com um e, em seguida, encontrar uma exceção, o recurso será liberado corretamente.
Chris

Respostas:

165

1 e 2

myVar = 8; //not dynamically allocated. Can't call delete on it.
myPointer = new int; //dynamically allocated, can call delete on it.

A primeira variável foi alocada na pilha. Você pode chamar delete apenas na memória alocada dinamicamente (no heap) usando o newoperador.

3 -

  myPointer = NULL;
  delete myPointer;

O acima exposto não fez nada . Você não liberou nada, pois o ponteiro apontava para NULL.


O seguinte não deve ser feito:

myPointer = new int;
myPointer = NULL; //leaked memory, no pointer to above int
delete myPointer; //no point at all

Você o apontou para NULL, deixando para trás memória perdida (o novo int que você alocou). Você deve liberar a memória para a qual estava apontando. Não há mais como acessar aquele alocado new int, daí vazamento de memória.


Da maneira correta:

myPointer = new int;
delete myPointer; //freed memory
myPointer = NULL; //pointed dangling ptr to NULL

A melhor maneira:

Se você estiver usando C ++, não use ponteiros brutos. Em vez disso, use ponteiros inteligentes, que podem lidar com essas coisas para você com pouca sobrecarga. C ++ 11 vem com vários .

Anirudh Ramanathan
fonte
13
<pedantry> "On the stack" é um detalhe de implementação - um que o C ++ visivelmente evita mencionar. O termo mais correto é "com duração de armazenamento automática". (C ++ 11, 3.7.3) </
pedantry
4
Obrigado, selecionei sua resposta para a) explicar o que estava errado eb) dar uma boa prática, muito obrigado!
leópico
6
@Tqn Isso não está certo. delete myPointerdesaloca *myPointer. Está correto. Mas myPointercontinua a apontar para um local de memória que foi liberado e não deve ser usado porque é UB. Ela ficará inacessível após o fim do escopo apenas SE for uma variável local em primeiro lugar.
Anirudh Ramanathan
2
Obrigado @DarkCthulhu! (Eu literalmente) aprendo algo newtodos os dias. (Eu sou cafona!)
Tqn
1
@AmelSalibasic A memória associada à variável na pilha será liberada apenas quando sair do escopo. Atribuir a NULLevita que o façamos mal posteriormente.
Anirudh Ramanathan
23

Eu acredito que você não está entendendo totalmente como os ponteiros funcionam.
Quando você tem um ponteiro apontando para alguma memória, há três coisas diferentes que você deve entender:
- há "o que é apontado" pelo ponteiro (a memória)
- este endereço de memória
- nem todos os ponteiros precisam ter sua memória excluída: apenas você precisa excluir a memória que foi alocada dinamicamente ( newoperador usado ).

Imagine:

int *ptr = new int; 
// ptr has the address of the memory.
// at this point, the actual memory doesn't have anything.
*ptr = 8;
// you're assigning the integer 8 into that memory.
delete ptr;
// you are only deleting the memory.
// at this point the pointer still has the same memory address (as you could
//   notice from your 2nd test) but what inside that memory is gone!

Quando você fez

ptr = NULL;
// you didn't delete the memory
// you're only saying that this pointer is now pointing to "nowhere".
// the memory that was pointed by this pointer is now lost.

C ++ permite que você tente deleteum ponteiro que aponta para, nullmas na verdade não faz nada, apenas não dá nenhum erro.

Salgadokk
fonte
2
Obrigado, ISSO foi super útil, pensei que TINHA que deletar todos os ponteiros, não sabia que era só para os que estavam novos, obrigado.
leópico
13

Os ponteiros são semelhantes às variáveis ​​normais no sentido de que você não precisa excluí-los. Eles são removidos da memória no final da execução de uma função e / ou no final do programa.

No entanto, você pode usar ponteiros para alocar um 'bloco' de memória, por exemplo:

int *some_integers = new int[20000]

Isso alocará espaço de memória para 20.000 inteiros. Útil, porque a pilha tem um tamanho limitado e você pode querer mexer com uma grande carga de 'ints' sem um erro de estouro de pilha.

Sempre que você chamar new, você deve então 'deletar' no final do seu programa, porque caso contrário, você terá um vazamento de memória e algum espaço de memória alocado nunca será retornado para outros programas usarem. Para fazer isso:

delete [] some_integers;

Espero que ajude.

user3728501
fonte
1
Eu quero apenas adicionar que a memória alocada SERÁ devolvida para outros programas usarem, mas somente APÓS o término da execução do programa.
sk4l
7

Existe uma regra em C ++, para cada novo há uma exclusão .

  1. Por que o primeiro caso não funciona? Parece o uso mais simples de usar e excluir um ponteiro? O erro diz que a memória não foi alocada, mas 'cout' retornou um endereço.

novo nunca é chamado. Portanto, o endereço que cout imprime é o endereço da localização da memória de myVar, ou o valor atribuído a myPointer neste caso. Por escrito:

myPointer = &myVar;

você diz:

myPointer = O endereço de onde os dados em myVar são armazenados

  1. No segundo exemplo o erro não está sendo disparado mas fazer um cout do valor de myPointer ainda retorna um endereço de memória?

Ele retorna um endereço que aponta para um local da memória que foi excluído. Porque primeiro você cria o ponteiro e atribui seu valor a myPointer, segundo você o exclui, terceiro você o imprime. Portanto, a menos que você atribua outro valor a myPointer, o endereço excluído permanecerá.

  1. # 3 realmente funciona? Parece que funciona, o ponteiro não está mais armazenando um endereço. Essa é a maneira correta de excluir um ponteiro?

NULL é igual a 0, você exclui 0, portanto, não exclui nada. E é lógico que ele imprima 0 porque você:

myPointer = NULL;

que é igual a:

myPointer = 0;
FonZ
fonte
4
  1. Você está tentando excluir uma variável alocada na pilha. Você não pode fazer isso
  2. Excluir um ponteiro não destrói um ponteiro, na verdade, apenas a memória ocupada é devolvida ao sistema operacional. Você pode acessá-lo até que a memória seja usada para outra variável ou manipulada de outra forma. Portanto, é uma boa prática definir um ponteiro para NULL (0) após a exclusão.
  3. A exclusão de um ponteiro NULL não exclui nada.
Hakan Serce
fonte
1
int value, *ptr;

value = 8;
ptr = &value;
// ptr points to value, which lives on a stack frame.
// you are not responsible for managing its lifetime.

ptr = new int;
delete ptr;
// yes this is the normal way to manage the lifetime of
// dynamically allocated memory, you new'ed it, you delete it.

ptr = nullptr;
delete ptr;
// this is illogical, essentially you are saying delete nothing.
Casper Beyer
fonte
1
Além disso, confira esta palestra sobre stack frames youtube.com/watch?v=bjObm0hxIYY e youtube.com/watch?v=Rxvv9krECNw sobre ponteiros.
Casper Beyer