C++ delete - It deletes my objects but I can still access the data?
Asked Answered
B

13

43

I have written a simple, working tetris game with each block as an instance of a class singleblock.

class SingleBlock
{
    public:
    SingleBlock(int, int);
    ~SingleBlock();

    int x;
    int y;
    SingleBlock *next;
};

class MultiBlock
{
    public:
    MultiBlock(int, int);

    SingleBlock *c, *d, *e, *f;
};

SingleBlock::SingleBlock(int a, int b)
{
    x = a;
    y = b;
}

SingleBlock::~SingleBlock()
{
    x = 222;
}

MultiBlock::MultiBlock(int a, int b)
{
    c = new SingleBlock (a,b);
    d = c->next = new SingleBlock (a+10,b);
    e = d->next = new SingleBlock (a+20,b);
    f = e->next = new SingleBlock (a+30,b);
}

I have a function that scans for a complete line, and runs through the linked list of blocks deleting the relevant ones and reassigning the ->next pointers.

SingleBlock *deleteBlock;
SingleBlock *tempBlock;

tempBlock = deleteBlock->next;
delete deleteBlock;

The game works, blocks are deleted correctly and everything functions as it is supposed to. However on inspection I can still access random bits of deleted data.

If I printf each of the deleted singleblocks "x" values AFTER their deletion, some of them return random garbage (confirming the deletion) and some of them return 222, telling me even though the destructor was called the data wasn't actually deleted from the heap. Many identical trials show it is always the same specific blocks that are not deleted properly.

The results:

Existing Blocks:
Block: 00E927A8
Block: 00E94290
Block: 00E942B0
Block: 00E942D0
Block: 00E942F0
Block: 00E94500
Block: 00E94520
Block: 00E94540
Block: 00E94560
Block: 00E945B0
Block: 00E945D0
Block: 00E945F0
Block: 00E94610
Block: 00E94660
Block: 00E94680
Block: 00E946A0

Deleting Blocks:
Deleting ... 00E942B0, X = 15288000
Deleting ... 00E942D0, X = 15286960
Deleting ... 00E94520, X = 15286992
Deleting ... 00E94540, X = 15270296
Deleting ... 00E94560, X = 222
Deleting ... 00E945D0, X = 15270296
Deleting ... 00E945F0, X = 222
Deleting ... 00E94610, X = 222
Deleting ... 00E94660, X = 15270296
Deleting ... 00E94680, X = 222

Is being able to access data from beyond the grave expected?

Sorry if this is a bit long winded.

Briefs answered 18/12, 2009 at 20:20 Comment(6)
The safest policy is to delete an item when it is no longer used, and never refer to it again. Smart Pointers can help when more than one pointer is referring to the same object in memory.Lafontaine
If you can access the blocks, you can re-delete them. That's bad. Don't do it.Bela
Sometimes I think a better keyword than delete would have been forget; you're not actually telling the compiler to delete anything so much as stop caring about it (and letting someone else do whatever they want with i) kind of like returning a book to the library rather than burning it.Sepaloid
The way this code is structured, the Multiblock class isn't responsible for handling it's own members. While this is legal C++ (it compiles, and doesn't rely on undefined behavior - ignoring the access after delete that you're talkinga about here), it is really a C-styled program. Try to make MultiBlock handle its own members, including delete operations. If it isn't too difficult, avoid exposing the raw pointers outside the class. This encapsulation will generally save you from a whole host of bugs/memory leaks down the line.Slink
I agree with Thomas Matthews. Use smart pointers if you can (boost library shared_pointer is a pretty good general purpose one). If you don't want to take the library dependency, try using a std::list or std::vector instead of manually coding a linked list/heap-allocated expandable array implementation.Slink
This is a case for best practices.Insight
C
96

Is being able to access data from beyond the grave expected?

This is technically known as Undefined Behavior. Don't be surprised if it offers you a can of beer either.

Corettacorette answered 18/12, 2009 at 20:22 Comment(4)
Also, it is good to add the corollary of that fact... If one had data that is "sensitive" stored in memory, one should consider it is a good practice to completely overwrite it before deleting it (in order to prevent other segments of code from accessing it).Wide
That ought to be handled before the dtor call.Corettacorette
@dirkgently: Yeah, I think the destructor is the right place. You don't want to do it too soon, and you can't do it too late.Bela
@Romain: One just has to make really sure it's not optimized out, as it's not observable behavior. (Use an API-function guaranteed not to be pruned, not memset.)Longeron
A
44

Is being able to access data from beyond the grave expected?

In most cases, yes. Calling delete doesn't zero the memory.

Note that the behavior is not defined. Using certain compilers, the memory may be zeroed. When you call delete, what happens is that the memory is marked as available, so the next time someone does new, the memory may be used.

If you think about it, it's logical - when you tell the compiler that you are no longer interested in the memory (using delete), why should the computer spend time on zeroing it.

Apetalous answered 18/12, 2009 at 20:22 Comment(3)
However, there is no guarantee that new or malloc won't allocate some new objects on top of the old ones. Another disaster may be the system garbage collector. Also, if your program is granted memory from a system-wide memory pool, other programs may write over the ghost data.Lafontaine
Actually, no. Successfully accessing deleted memory is not expected behavior, it's undefined behavior. Another allocation could just as easily overwrite the memory you just freed.Lopeared
@Thomas Matthews I'm not saying that it's a good idea trying to access it. @Curt Nichols That's playing with words. Depending on what compiler you use, you can expect that the memory isn't zeroed immediately when calling delete. You can obviously not be sure about it though.Apetalous
N
18

Delete doesn't delete anything -- it just marks the memory as "being free for reuse". Until some other allocation call reserves and fills that space it will have the old data. However, relying on that is a big no-no, basically if you delete something forget about it.

One of the practices in this regard that is often encountered in libraries is a Delete function:

template< class T > void Delete( T*& pointer )
{
    delete pointer;
    pointer = NULL;
}

This prevents us from accidentally accessing invalid memory.

Note that it is perfectly okay to call delete NULL;.

Nose answered 18/12, 2009 at 20:32 Comment(10)
Even if you don't use a macro, it's good practice to set a pointer to NULL immediately after freeing it. It's a good habit to get into, preventing these sorts of misunderstandings.Eleaseeleatic
@Kornel Any C++ library that used such a macro would be extremely suspect, IMHO. At the very leasy, it should be an inline template function.Pestilential
@Mark Setting pointers to NULL following delete is not universal good practice in C++. There are times when it is a good thing to do, and times when it is pointless and can hide errors.Pestilential
@Niel OGRE? And a few more that I've seen. But yes, a template function is a lot better, and I fully agree on this practice :)Nose
I hate this practice. It's very cluttering, and meh.Big
"This prevents us from accidentally accessing invalid memory". This is not true, and it demonstrates why using this trick should be expected to be correlated with writing bad code. char *ptr = new char; char *ptr2 = ptr; Delete(ptr); *ptr2 = 0;. I accidentally accessed invalid memory. It's just muddled thinking to null a reference, in the belief that this protects the object referred to. Also, don't forget that you'd need a separate version of this function for pointers to arrays.Isentropic
@Neil when would setting the pointer to NULL after deleting hide an error?Kora
@Franci: when you have a function that's only supposed to be called once, that deletes the pointer and then nulls it. Some erroneous code elsewhere calls your function twice, and nothing goes wrong, despite the fact that this other code has a double-free error, which could easily turn into a user-after-free error in future, or in slightly different circumstances.Isentropic
Of course you can check for null before deleting (and hence catch the double-free error), but then you lose the ability to store null in that field in non-error cases.Isentropic
You can't rely on being able to read the data even without any more allocations, as the allocator is free to use it for any purpose, such as a linked list for storing free blocks.Protist
P
10

It is what C++ calls undefined behaviour - you might be able to access the data, you might not. In any case, it is the wrong thing to do.

Pestilential answered 18/12, 2009 at 20:22 Comment(0)
B
6

Heap memory is like a bunch of blackboards. Imagine you are a teacher. While you're teaching your class, the blackboard belongs to you, and you can do whatever you want to do with it. You can scribble on it and overwrite stuff as you wish.

When the class is over and you are about to leave the room, there is no policy that requires you to erase the blackboard -- you simply hand the blackboard off to the next teacher who will generally be able to see what you wrote down.

Bangup answered 18/12, 2009 at 21:12 Comment(1)
If a compiler can determine that code is inevitably going to access (even look at) part of the blackboard it doesn't own, such determination will free the compiler from the laws of time and causality; some compilers exploit that in ways that would have been considered absurd a decade ago (many of which still are absurd, IMHO). I could understand saying that if two pieces of code don't depend on each other a compiler may interleave their processing in any fashion even if that causes UB to hit "early", but once UB becomes inevitable all rules fly out the window.Eleazar
P
3

The system does not clear the memory when you release it via delete(). The contents are therefore still accessible until the memory is assigned for reuse and overwritten.

Path answered 18/12, 2009 at 20:22 Comment(2)
It is nonetheless not allowed to access the object after it has been deleted. It doesn't matter what contents the memory has.Vasoconstrictor
"still accessible" only in the sense that the other side of an active minefield is still accessible -- i.e. you might get away with it, but you're also quite likely to get blown up if you try, so you'd better not take the risk.Weariful
J
3

After deleting an object it's not defined what will happen to the contents of the memory that it occupied. It does mean that that memory is free to be re-used, but the implementation doesn't have to overwrite the data that was there originally and it doesn't have to reuse the memory immediately.

You shouldn't access the memory after the object is gone but it shouldn't be surpising that some data remains in tact there.

Jugurtha answered 18/12, 2009 at 20:23 Comment(0)
C
1

delete deallocates the memory, but does not modify it or zero it out. Still you should not access deallocated memory.

Carcinomatosis answered 18/12, 2009 at 20:22 Comment(1)
It is not specified whether the memory will be zeroed or not. E.g. an implementation might overwrite the memory after delete for debugging or security purposes.Vasoconstrictor
D
1

Yes, it can be expected at times. Whereas new reserves space for data, delete simply invalidates a pointer created with new, allowing data to be written at the previously reserved locations; it doesn't necessarily delete the data. However, you shouldn't rely on that behaviour because the data at those locations could change at any time, possibly causing your program to misbehave. This is why after you use delete on a pointer (or delete[] on an array allocated with new[]), you should assign NULL to it so that you can't tamper with an invalid pointer, assuming you won't allocate memory using new or new[] before using that pointer again.

Dismount answered 18/12, 2009 at 20:25 Comment(1)
There is nothing in the C++ language standard preventing delete from erasing the memory that has been deleted or filling with a strange value. It is implementation defined.Lafontaine
C
0

It won't zero/change memory just yet... but at some point, the rug is going to be pulled from under your feet.

No it is certainly not predictable: it depends on how fast memory allocation/deallocation is churned.

Cystotomy answered 18/12, 2009 at 20:22 Comment(1)
It may zero the memory immediately. There is nothing in the language standard preventing it and it might make sense for debugging or security reasons. In any case, accessing the object after the delete call is UB.Vasoconstrictor
H
0

Although it's possible that your runtime doesn't report this error, using a proper error-checking runtime such as Valgrind will alert you to the use of memory after it has been freed.

I recommend that if you write code with new/delete and raw pointers (rather than std::make_shared() and similar), that you exercise your unit tests under Valgrind to at least have a chance of spotting such errors.

Hostelry answered 31/8, 2016 at 10:3 Comment(0)
T
-1

It will lead to undefined behaviour and delete deallocates memory , it does not reinitialize it with zero .

If you want to make it zero out then do :

SingleBlock::~SingleBlock()

{    x = y = 0 ; }
Trey answered 18/12, 2009 at 20:39 Comment(1)
This is not a safe way of clearing memory. The compiler would probably optimize away the stores. And when the destructor has been called, you are still not allowed anymore to access the object.Vasoconstrictor
G
-3

Well, I have been wondering about this for quite a while as well, and I have tried to run some tests to better understand what's going on under the hood. The standard answer is that after you call delete you should not expect anything good from accessing that memory spot. However, this did not seem enough to me. What is it really happening when calling delete(ptr)? Here's what I've found. I'm using g++ on Ubuntu 16.04, so this may play a role in the results.

What I first expected when using the delete operator was that the freed memory would be handed back to the system for usage in other processes. Let me say this does not happen under any of the circumstances I have tried.

Memory released with delete still seem to be allocated to the program it first allocated it with new. I have tried, and there is no memory usage decrease after calling delete. I had a software which allcated around 30MB of lists through new calls, and then released them with subsequent delete calls. What happened is that, looking at the System monitor while the program was running, even a long sleep after the delete calls, memory consumption my the program was the same. No decrease! This means that delete does not release memory to the system.

In fact, it looks like memory allocated by a program is his forever! However, the point is that, if deallocated, memory can be used again by the same program without having to allocate any more. I tried to allocate 15MB, freeing them, and then allocating another 15MB of data after, and the program never used 30MB. System monitor always showed it around 15MB. What I did, in respect to the previous test, was just to change the order in which things happened: half allocation, half deallocation, other half of allocation.

So, apparently memory used by a program can increase, but never shrink. I thought that maybe memory would really be released for other processes in critical situations, such as when there is no more memory available. After all, what sense would it make to let a program keep its own memory forever, when other processes are asking for it? So I allocated the 30MB again, and while deallocating them I run a memtester with as much physical memory I could. I expected to see my software hand out its memory to memtester. But guess it, it did not happen!

I've made up a short screencast that shows the thing in action:

Delete example memory

To be 100% honest, there was a situation in which something happened. When I tried memtester with more than the available physical memory in the middle of the deallocation process of my program, the memory used by my program dropped to around 3MB. The memtester process was killed automatically though, and what happened was even more surprising! The memory usage of my program increased with each delete call! It was just as if Ubuntu was restoring all its memory back after the memtester incident.

Taken from http://www.thecrowned.org/c-delete-operator-really-frees-memory

Galcha answered 6/3, 2017 at 15:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.