1

I am learning about pointers in C++ currently, in college. I have coded a program that is a binary tree of objects that points to a linked list of sub-objects. IF I am even wording that correctly. Anyways, my program seems to work correctly, but I am having trouble wrapping my head around how to test pointer deletion.

For instance, my delete function for single object of the binary tree is:

    void EmployeeRecord::destroyCustomerList()
    {
        if(m_oCustomerList != NULL)
        {
            delete m_oCustomerList;
            m_oCustomerList = NULL;           
        }
    }

When printing my tree, everything populates and is taken off correctly (meaning the tree is kept intact through every removal of a node)...but how do I confirm what happens to the deallocated memory? I know that since I am setting the pointer *m_oCustomerList to NULL, that I can test for a NULL value on a previously populated object, but what happens to the actual memory?

I am using Visual Studio/C++ and have read that the debugger will use a code starting at 0xCC for deallocated memory...but I can't seem to figure out how to use that information.

user2079828
  • 645
  • 1
  • 8
  • 31
  • 2
    Why do you want to verify it? Do you doubt that the compiler is doing it's job properly? – JBentley Apr 04 '13 at 21:10
  • 1
    You can use [Valgrind](http://valgrind.org/) to help detect and debug memory leaks. – Jared Ng Apr 04 '13 at 21:10
  • Possible duplicate of http://stackoverflow.com/questions/15730827/how-to-detect-if-pointer-was-deleted-and-securely-delete-it?rq=1 ? I would either use smart_ptrs or add something to the destructor for debugging/testing purposes to confirm deletion. – drquicksilver Apr 04 '13 at 21:10
  • 2
    There's really no reason to verify that `delete` works. It does. What happens to the memory when you delete is up to the compiler designer, but generally it's returned to a heap and made available for future use. – Carey Gregory Apr 04 '13 at 21:11
  • You could also use a garbage collector and not have to delete at all. – user1095108 Apr 04 '13 at 21:13
  • @user1095108, Who needs a garbage collector when we have RAII? :p – chris Apr 04 '13 at 21:16
  • I do not doubt the compiler. I think that I just want to know everything I can, useful or not. I'm a student trying to learn everything I can about something I may be doing the rest of my life and I just wanted clarification. I'm trying to figure out what exactly happens when I delete a class pointer that points to a class pointer that points to a class pointer that points to a class pointer. I assume all the destructors are called for each pointer deleted? – user2079828 Apr 04 '13 at 21:23
  • @user2079828 Nope. Well what is a pointer which points to a pointer? If you mean `**ptr` then no, the inner destructor is not called. If you mean that the outer object contains a pointer as a member, then that only gets deleted if the class has a destructor function which does so. Whether or not it should do that depends on the case - it may not be the right thing to do, if other pointers to the same object will exist elsewhere. – drquicksilver Apr 04 '13 at 21:43
  • Sorry, I should say that each object being pointed to contains(as one of its members) one or more pointers to other class objects, and those class objects contain pointers to a different class' object. – user2079828 Apr 04 '13 at 21:56
  • When `delete`ing an object, its destructor gets called. This might cause deletion of other objects and calling of other objects' destructors. It might be instructive for you to step through destruction of objects in a debugger to see how all of this unfolds. – Magnus Hoff Apr 04 '13 at 22:01
  • Ok, I put breakpoints at each destructor. The four destructors of each class seem to be called at the appropriate time, confirming memory deletion. I don't know why I didn't think of that earlier. Thank you Magnus. – user2079828 Apr 04 '13 at 22:01
  • Did someone say Valgrind? You can use Valgrind, sure: if you first port the code from Visual Studio to Linux. – Kaz Apr 09 '13 at 01:30

4 Answers4

7

Note that your code

    void EmployeeRecord::destroyCustomerList()
    {
        if(m_oCustomerList != NULL)
        {
            delete m_oCustomerList;
            m_oCustomerList = NULL;           
        }
    }

Simplifies to:

    void EmployeeRecord::destroyCustomerList()
    {
        delete m_oCustomerList;
        m_oCustomerList = NULL;
    } 

It is safe to invoke the delete operator on a null pointer in C++. It does nothing. In other words, the check for null is already "built in".

Once you delete an object, it no longer exists, and the pointer to that object becomes and indeterminate value (so it's not a bad idea to null out all copies of that pointer).

What really happens to the memory in actual C++ implementations, rather than in the abstract sense, is that it continues to exist at the same address, but is marked as free, so that it can be allocated for another purpose. An allocation request coming from the program (possibly a completely unrelated module) or possibly from another program in the system, could obtain that memory for its own use.

Any uses of a pointer to an object which no longer exists are "undefined behavior". Functions for safely verifying such a pointer do exist, but they are very platform-specific and rarely perfect.

The problem is that whereas it is not particularly hard for an implementation to confirm that a pointer is bad, it is not possible to confirm that a pointer is good. We can walk the internal memory data structures of the memory allocator to determine that some pointer refers to free storage. But what if the storage is subsequently allocated? Then the pointer no longer refers to free storage. But it does not refer to the original object which was allocated, either! This is known as an "ABA ambiguity": because some A changed into a B, but then back into A, indistinguishable from the original A.

Approaches exist to solve the ABA ambiguity (if not completely than at least partially). For instance, pointers be made "fat" so the they have an extra field in addition to the address bits. The field could contain a sequence number which is used to stamp the pointer that are returned from the allocator. Now when an object is deleted and reallocated, the new pointer to the same location has a different sequence number: we have ABA'. The pointer A has gone bad, making it B, but the when it is resurrected it comes back as A'. If we ask the system to validate A, it will correctly determine that A is bad, because it does not have the expected sequence number. The correct, valid pointer to the object is A', which does not match A.

However, sequence number fields are only so many bits wide and they will wrap around eventually. So the ABA problem has not really been solved. The validation of good versus bad pointers has only been made substantially more reliable. To absolutely deal with the ABA problem, the system must always hand out new pointers which are not equal to any pointers which could still be in use. This means never actually freeing anything (thereby running out of memory) or implementing garbage collection. (Meaning that delete actually does nothing: deleted objects are destructed, but stick around in memory until they are garbage-collected, which happens when the program no longer remembers any copies of the pointer. At that point, the program no longer remembers A, and so A can be re-introduced, and there is no ABA problem.)

To make all pointers "fat", you have to change the entire toolchain and runtime: compilers, libraries, et cetera. There are further difficulties because large programs tend to have multiple memory allocators. If you ask the wrong allocator "is this pointer valid", all it can say is "this pointer is not from my arena". Another approach you can do is to invent your own pointers and implement them as smart pointers in C++. Your pointers can support an is_valid method which tries to be as reliable as possible (dealing with the ABA problem somehow: either partially with some sequence numbers and such, or by implementing your own garbage collection scheme.)

Kaz
  • 55,781
  • 9
  • 100
  • 149
2

Accessing deleted memory is undefined behaviour by the standard. For instance, if this was a multithreaded application (or some other process had injected a thread into your application) then a new allocation could allocate the memory you just deallocated before you are able to "verify" it.

Neil
  • 54,642
  • 8
  • 60
  • 72
0

Once you delete your memory and set your pointer to NULL you no longer have access to that memory even if you want it. So, there is no way to verify that it really gone. However, if you did something wrong and the memory was never deleted it would consist of a memory leak which would cause your program to increase the amount of ram it uses, you could see this as a symptom of a pointer not properly disposed of.

You will probably learn later that you will not have to worry about the deletion of your pointers because of std::shared_ptr which will delete your object when the pointer goes out of scope. Which will be safer later on because you will probably will learn that exceptions can cause your destructor to never fire leaving a memory leak.

Travis Pessetto
  • 3,260
  • 4
  • 27
  • 55
-2
...
... 
delete m_oCustomerList;

// Try using the deleted pointer here
// This should cause a runtime exception
// which means you did free the pointer
m_oCustomerList->someStrMemberVariable = "This will fail"
...
...

Needless to say, don't do this in the actual code. Hope this helps.

Vaibhav Desai
  • 2,618
  • 1
  • 16
  • 16
  • 4
    This answer is wrong. Accessing a deleted pointer is undefined behaviour, and is not guaranteed to cause a runtime error. – JBentley Apr 04 '13 at 21:15
  • I agree, for me at least...trying to access a NULL pointer and a deleted pointer seems to do the same thing...ie crash the program. – user2079828 Apr 04 '13 at 21:25
  • @user2079828 Accessing a null pointer will generate a runtime error on almost all operating systems, but accessing a deleted pointer is a matter of luck. In most cases, the pointer will still point to valid memory and no runtime error will occur. – Carey Gregory Apr 04 '13 at 21:44