Why doesn't deleting a pointer make it unusable?
Asked Answered
S

8

16

So to understand new/delete better (really to prove to myself with small examples why virtual destructors are needed for interfaces), I want to understand memory leaks, so that I may live in fear of them. But I am having a hard time getting my leak on, so to speak; actually, I am have a hard time with new/delete too.

Here's my simplest version:

int* P1 = new int(43);

cout << "P1 = " << P1 << endl;
cout << "*P1 = " << *P1 << endl;

delete P1;

cout << "P1 = " << P1 << endl;
cout << "*P1 = " << *P1 << endl;

This prints:

P1 = 0xcc0340
*P1 = 43
P1 = 0xcc0340
*P1 = 43

I had something more complicated inside of a class, but this example illustrates my fail. I thought delete takes a pointer and frees it's memory, thereby invalidating the pointer or at least what it points to? I must be doing something very simple very wrong.

Sauter answered 6/8, 2011 at 10:57 Comment(4)
@Bla: Nah, that's just for deleting pointers to arrays. Only use that if created with new[].Sauter
Because delete pointer means "delete the object pointed to by the pointer". It does not mean "delete the pointer".Digestant
@Fred: thanks, I just had that same thought a moment ago... there is no way to "delete the pointer" is there? I know it gets killed out of scope, and maybe I can hide it with scope, but I cannot explicitly call a pointer's destructor, can I?Sauter
@Jimmy: You can set the pointer to 0 (NULL), and if it remains in scope after it has been deleted, it is good practice to do so.Agata
S
26

You are causing undefined behaviour. This means that anything can happen. Since something did indeed happen, everything behaves as documented. (Sometimes "something" looks very similar to something else that you might erroneously expect. Doing exactly what you think you were trying to achieve is one of the possible allowed instances of "undefined behaviour".)

Note also that a "memory leak" is sort of the opposite of what you're trying to do - in a memory leak you forget to free memory, whereas you already freed the memory and are now accessing invalid memory.

Here's a real memory leak, which also does not cause undefined behaviour -- don't confuse "bad but correct" with "incorrect" programming!

int * factorial(int * n)
{
  if (*n == 0) return new int(1);
  else return new int(*n * *factorial(*n - 1));
}
Signify answered 6/8, 2011 at 10:59 Comment(7)
Gotcha. What is the behavior of delete supposed to be? Free up the memory pointed to, invalidate the pointer, or both?Sauter
If you invoke delete p for the first time and p is the result of a new expression, then the destructor will be called and the memory will be freed. Anything else is undefined behaviour. Note that your undefined behaviour comes from accessing the memory after the delete call.Signify
@Jimmy: It's both. The fact that the pointer invalidation might be delayed is an implementation detail- it's still completely illegal to access it. The OS can only allocate and de-allocate pages in certain chunks and it's a well-known implementation behaviour to keep these chunks around to re-use them for increased speed.Haemo
@Jimmy: Read this excellent answer for a description of why your bible was untouched.Signify
@Sauter - It frees the memory. After the memory is freed, the pointer points to unallocated memory, and therefore dereferencing it is undefined behavior. But just because the memory is freed doesn't mean that the old value is overwritten. You're more likely (but not guaranteed) to see a problem if you allocate a new pointer, set it to a new value, then check the old one. If the new one is allocated where the old one used to be, the old one will appear to have changed. (Or perhaps the old space is being used to store bookkeeping data for the memory manager, which yields even stranger results.)Learnt
this is a terrible answer, because You did not explain (in the answer) what is actually happening or why the behavior is undefined.Transferase
@Martin: I did say "you are accessing invalid memory". What else do you want to know? The question had also been retitled long after I posted the answer.Signify
L
23

I must be doing something very simple very wrong.

You're absolutely right. Your code is doing something very simple very wrong, and you've paid the ultimate price - not seeing any negative results. Sometimes, you do things wrong, and nothing bad happens. This is the worst possible outcome, because you may not realize your code is wrong, and you'll continue to use it, until one day your code breaks unexpectedly, and you'll have no idea where or how because it's always worked before. It'll break somewhere completely irrelevant to the actual part that's wrong, and you'll spend hours trying to track it down and figure out why it's broken.

When you do things wrong, it's undefined behavior. That means anything can happen. At best, your code crashes and segfaults and blinking lights come on saying "YOU'RE USING FREED MEMORY" and everything is very clear. At worst, demons fly out of your nose. But just above that, the second worst possible outcome is that everything appears to work as you intended it to.

Learnt answered 6/8, 2011 at 11:4 Comment(1)
+1 for "...and you've paid the ultimate price - not seeing any negative results."Kettering
S
5

This would be a memory leak:

int* P1 = new int(43);
     P1 = new int(42);

Allocating memory without deleting it again.

Supplementary answered 6/8, 2011 at 11:3 Comment(0)
N
4

This code

delete P1;

cout<<"P1 = "<<P1<<endl;
cout<<"*P1 = "<<*P1<<endl;

causes undefined-behavior. So anything can happen. It's much easier to cause a memory leak:

for(;;) new int;
Nevlin answered 6/8, 2011 at 11:0 Comment(10)
More like "memory suicide"! :-)Signify
running that program now, still have not crashed ;)Sauter
there it goes! finite dumping states, ending with a dump of core!!Sauter
@Kerrek: Yep, the OP wants to "live in fear of them". So I sincerely recommend him to run the above code ;-).Nevlin
You could crash much faster with for(;;) malloc((size_t)-1)Learnt
@Chris: this would not be a memory leak.Nevlin
The problem with the suicide is that it happens immediately and the program doesn't move beyond that point. The real fear of memory leaks isn't sudden death, though, but being called at 2am in a hotel room during an affair because your wife's business app that you wrote crashed during a used car sale.Signify
@ybungalobill - How about for(;;) malloc(4096);. Surely that'll leak a fair amount of memory before it crashes, and will crash faster than new int.Learnt
@Chris: c'mon, are we in a contest for 'who will leak memory faster'?Nevlin
@ybungalobill - Obviously. ;)Learnt
S
4
// Reserve some memory for an int and set that memory to the value 43.
int* P1 = new int(43);

// Print the address of the reserved memory.
cout<<"P1 = "<<P1<<endl;
// Print the contents of that memory.
cout<<"*P1 = "<<*P1<<endl;

// Free the memory - it is no longer reserved to you.
delete P1;

// int* P2 = new int(47);    

// Print the address of the memory. It still holds the address to 
// the memory that used to be reserved for you.
cout<<"P1 = "<<P1<<endl;

// Print the current value of the memory that used to be reserved.
cout<<"*P1 = "<<*P1<<endl;

If you would uncomment the P2 line, it is quite likely that it would be assigned the same memory which would change the value printed at the last line.

Accessing memory that has been freed with delete causes undefined behaviour as others have pointed out. Undefined includes crashing in strange ways on some cases (only during full moon perhaps? ;-). It also includes everything working perfectly well for now, but with the bug being a mine that can trip off whenever you make another change anywhere else in your program.

A memory leek is when you allocate memory with new and never free it with delete. This will usually not be noticed until someone runs your program for a longer period and finds out that it eats all the memory of the system.

Slovenia answered 6/8, 2011 at 11:4 Comment(0)
C
2

Dereferencing a deleted pointer is undefined behavior, as already explained, i.e. you are doing something wrong. Compilers may help you find that error by changing the pointer's value to some magic value that will always cause dereferencing the pointer to fail and can give you a clue that it's because it was previously deleted.

A quick test showed the following behavior for me:
MSVC2010:
debug build: *P1 = 0xfeeefeee
release build: *P1 = <random>
GCC 4.6:
debug & release builds: *P1 = 0

So you were just (un)lucky that you got again the original value. I recommend you to always build in debug mode for testing your programs (even if you aren't debugging) because it adds additional sanity checks to your code.

Cockerel answered 6/8, 2011 at 12:49 Comment(0)
G
1

The memory is freed but it is not cleaned. The value may stay in memory until some other process writes a new value in that location.

Gallo answered 6/8, 2011 at 11:0 Comment(3)
What does "clean" even mean? Is a zero bit any "cleaner" than a one bit? Is this bitism?Signify
@Kerrek SB "clean" doesn't have a formal meaning in this case. I meant to say that the value of the memory location was not changed. In a cryptographic application, it probably would have been required to overwrite the previous values.Gallo
The reason I feel uneasy about "clean" is that there's no way you could detect your purported clean state, let alone tell that the memory is no longer valid. I wouldn't want to imply that this could even be done at the level of raw pointers and memory.Signify
F
0

Delete doesn't invalidate the pointer. It releases the memory it points to back to the heap. In this case, the memory hasn't been reused and therefore still contains the same content. At some point that memory WILL be reused and then you get the undefined behavior that others speak of.

This isn't a memory leak though. That's when you point the pointer to another memory allocation (or just discard the pointer) without freeing the memory it points to. The first then stays allocated and, as you've no references to it, you've no way of freeing it.

Flagelliform answered 6/8, 2011 at 11:3 Comment(3)
Is the only way to "discard the pointer" to have it go out of scope? Since delete just frees the memory right? And there is no such thing as an explicit "undefine" is there? You know, like free up the name you had declared for your type...Sauter
The delete expression also invokes the destructor. There's a subtle difference between the delete operator and the delete expression...Signify
If you've got a pointer that's still in scope after having delete called through it, it's a good idea to set it to nul. That way you're pretty much guaranteed to get some sort of crash if you use it again. Whilst you're researching this subject take a look at REFERENCES and AUTOPOINTERS too as they can save you some of this grief. (Kerrek SB is right about the destructor being called too for object types.)Flagelliform

© 2022 - 2024 — McMap. All rights reserved.