what happens when deleting an object

Pages: 12
hi,
I want to understand the results that I am getting. I did some research but did get an answer for this. The code shows the results I got.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 #include <iostream>

class Foo {};

int main()
{
    auto foo = new Foo();

    std::cout << foo << std::endl;  // Result: 00A6F3E8
    delete foo;
    std::cout << foo << std::endl;  // Result: 00008123

    if (foo != nullptr)
   	std::cout << "foo is not null";
    else
	std::cout << "foo is null";
	
    // Result: "foo is not null"

}


My first question is if the results printed with "foo" is the memory address.
I was expecting an hexadecimal, so I am not sure what 00A6F3E8. Is this another way to represent an adress? and what about 00008123 after deleting. If I deleted the pointer shouldn't I be getting an error if I try to print something that doesn't exist? whish leads me to the next question. If theoretically deleted the pointer why I get "foo is not null"?
Is it because while in main(), all the variables are still alive even if deleted or something like that? Can someone shed some light on what is happening?

thanks,
R
Hello. I guess that you should read this explanation here about nullptr :
https://docs.microsoft.com/en-us/cpp/extensions/nullptr-cpp-component-extensions?view=msvc-170

Running your example in the embedded script compiler I have another logical output with the same address :

0x3e4d590
0x3e4d590
foo is not null 
Last edited on
1) Yes, This is a memory address as hexadecimal. If you run the program using the script compiler you'll get something like:


0x31da9f0
0x31da9f0



2) and what about 00008123 after deleting? I don't see this. The values before and after are the same - as per above. What os/compiler are you using?

delete tells the os that the specified memory is no longer required and can be re-used for other purposes. It doesn't change the value held by the specified variable. Just don't try to re-reference the memory specified after a delete. In many programs that use delete within code, you'll often also see something like:

1
2
delete foo;
foo = nullptr;


so that if foo is de-referenced after delete it will trigger an exception.
> My first question is if the results printed with "foo" is the memory address.
>I was expecting an hexadecimal

It is an 'implementation defined character sequence' describing a pointer. For object-pointers it is an address, usually in hexadecimal.


> If theoretically deleted the pointer why I get "foo is not null"?
> Is it because while in main(), all the variables are still alive

There are two objects involved:
1. An unnamed object of type Foo created with new Foo().
2. A named object foo of type 'pointer to Foo' which holds a pointer to the anonymous object.

After delete foo;, the unnamed object (1) of type Foo which was created with new is gone; its lifetime is over.
The second object (2), the pointer foo, is still there; its lifetime is not yet over.
delete foo; normally would not modify the pointer in any way; it would still hold the old address.
A pointer is really just a memory address. You can print it as a decimal or as hexadecimal number. Or as whatever you like. Hexadecimal simply is what people commonly use to print memory addrsses.

Now, you can not delete a pointer! You can delete the object that the pointer is pointing to. After the object has been deleted, the pointer still is pointing at the same memory address that it was pointing to before! In other words: When an object is deleted, pointers to that object do not "magically" become NULL pointers. Instead, the object, that the pointer is pointing to, simply no longer exists. We call this a "dangling" pointer.

Printing a "dangling" pointer is perfectly safe. After all, its just a memory address! And that address hasn't changed at all. Where things become dangerous is when you try to dereference a "dangling" pointer, i.e. when you try to access the object that the pointer is pointing to, even though that object no longer exists 😱

The memory area, where the deleted object was located before, may have been unmapped from your process' virtual address space, in which case any attempt to access that memory results in an "access violation" exception. But, it is just as well possible that this memory area was re-used for something else! In the latter case, you won't get an exception when trying to access that memory, but you will be reading some completely unrelated "garbage" data! Even worse, you might end up overwriting another object's data!
Last edited on
Now, you can not delete a pointer!


Yes you can. You can dynamically allocate a pointer, just like anything else. What you can't do is delete a variable that you didn't dynamically allocate in the first place:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int myVal = 12345;

// Dynamically allocate a pointer to an int, i.e. an int*, and store the address in an int**
int** dynamicPtr = new int*;

// Set the int pointer to point to myVal, i.e. to store the address of myVal
*dynamicPtr = &myVal;

std::cout << "Value of the integer being pointed to is: " << **dynamicPtr;

// We dynamically allocated the int* so we can delete it:
delete (*dynamicPtr);  // Works fine

// The value stored in dynamicPtr hasn't changed.  That value is still a memory 
// address, but the memory at that address has been freed up; it is no longer 
// available for use.  The data stored in the memory at that address cannot be 
// reliably predicted.  It might still be 12345, it might have been reset to some 
// default value, or it might have already been reused for something else.

// We didn't dynamically allocate the int** so we cannot delete it:
delete dynamicPtr;  // ERROR 

(Code not tested. I have almost certainly made mistakes :) )

If a pointer to a pointer is confusing, maybe this makes it a bit clearer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
typedef int* IntPtr;

int myVal = 12345;

// Dynamically allocate a pointer to an int, i.e. an int*, and store the address in an int**
IntPtr* dynamicPtr = new IntPtr;

// Set the int pointer to point to myVal, i.e. to store the address of myVal
*dynamicPtr = &myVal;

std::cout << "Value of the integer being pointed to is: " << **dynamicPtr;

// We dynamically allocated the IntPtr so we can delete it:
delete (*dynamicPtr);  // Works fine

// The value stored in dynamicPtr hasn't changed.  That value is still a memory 
// address, but the memory at that address has been freed up; it is no longer 
// available for use.  The data stored in the memory at that address cannot be 
// reliably predicted.  It might still be 12345, it might have been reset to some 
// default value, or it might have already been reused for something else.

// We didn't dynamically allocate the int** so we cannot delete it:
delete dynamicPtr;  // ERROR 

(Code not tested. I have almost certainly made mistakes :) )

The trick is to remember that a pointer is a variable like any other (one that stores a single integer, that being a memory address). It follows the same rules as any other variable.

And to forestall the obvious responses: this is not me being pointlessly pedantic for my own gratification. This is trying to help learners understand exactly what pointers really are, and to stop them "learning" incorrect things about what you can and can't do with them.
Last edited on
Hello,
thanks all for the reply and the resources. I think it is a bit more clear now.
About what a os and complier I am using it is windows with visual studio.

I think it is important to clarify though, that a pointer is not a memory address as it was mentioned in a reply. A pointer is a variable holding the address of another variable( it "points" to another address) as MikeyBoy mentioned.
It might seem innocent clarification but I think it is quite important.

Thanks again.

R

ps: why was MikeyBoy reply reported? nothing wrong with it.
Last edited on
Well, DAYUM! Someone sure is getting report happy.....
A pointer is a memory address. This can be the address of a global (statically allocated) variable, it can be the address of a local (stack allocated) variable, it can the address of a block of memory (e.g. object) that was dynamically allocated on the heap, and it even can be an arbitrary/invalid memory address.

Even though you can store a pointer (memory address) in a variable, it is not necessary to store a pointer in a separate variable for that pointer to exist! For example, the & operator gives you the address of (or, in other words, "a pointer to") its operand. You don't have to store that pointer in a variable to be able to use it:
1
2
3
4
5
6
7
8
9
10
11
12
int foo = 13;

int test(void)
{
    int bar = 42:
    MyClass *obj = new MyClass();

    printf("Pointer to the global (statically allocated) variable 'foo': %p\n", &foo);
    printf("Pointer to the local (stack allocated) variable 'bar': %p\n", &bar);
    printf("Pointer to the MyClass object allocated on the heap space: %p\n", obj);
    printf("Pointer to an arbitrary and probably invalid memory location: %p\n", (void*)666);
}

Form the official documentation:
The unary address-of operator (&) returns the address of (that is, a pointer to) its operand.
- https://docs.microsoft.com/en-us/cpp/cpp/address-of-operator-amp?view=msvc-170

______________


Going back to the original question:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void test(void)
{
    /* Allocate an instance (object) of class MyClass on the stack (will be destroyed when it goes out of scope) */
    MyClass obj;

    /* Allocate an instance of class MyClass on the heap space, and store the pointer returned by the "new" operator in pointer variable 'ptr' */
    MyClass *ptr = new MyClass();

    /* Print the addresses of (i.e. "pointer to") the two objects */
    printf("Pointer to the first (stack-allocated) instance: %p\n", &obj);
    printf("Pointer to the second (heap-allocated) instance: %p\n", ptr);

    /* Actually access the two objects */
    obj.do_something();
    ptr->do_something();

    /* Destory the object that was allocated on the heap */
    delete ptr;

    /* The variable 'ptr' still contains the exactly same pointer (memory address) as before, even though the object at that address has been destroyed */
    printf("Pointer to the location where the now-destroyed object used to reside: %p\n", ptr);

    /* The following line is a dangerous "use after free" vulnerability */
    /* At this point, the memory region that the memory address in 'ptr' is pointing to may have been un-mapped or re-used for something entirely different !!! */
    ptr->do_something();
}

void test2(void)
{
    /* The following is a "memory leak", because the new heap-allocated object is never delete'd, but it's still perfectly possible to do that */
    /* Note: We *never* store the pointer to the object in a variable, but still we access the object via that pointer ;-) */
    (new MyClass())->do_something();
}
Last edited on
In the original post, the value of the pointer itself (the address) somehow changed after the call to delete. I am still perplexed by this, which exact version of Visual Studio is this? (Go to Help --> About, or something like that)

I am assuming that Visual Studio is doing some sort of "extension" in behavior by marking the deleted pointer to cause a better error message if you were to dereference it after deletion. If you build in release mode, do you get the same behavior (a change in the address after calling delete)?

Edit 1:
https://stackoverflow.com/questions/20725030/address-held-by-pointer-changes-after-pointer-is-deleted
I'm still confused after reading the above link. Maybe it's implementation-defined behavior?

Edit 2:
Okay, I'm just going to trust Stroustrup on this. Apparently a call to delete can mutate the pointer itself, so it's implementation-defined.
https://www.stroustrup.com/bs_faq2.html#delete-zero
C++ explicitly allows an implementation of delete to zero out an lvalue operand

If it's not an lvalue, it's guaranteed to stay the same, so if you did delete foo + 0;, then foo is guaranteed to still be the same value as before.

things_learned++;
Last edited on
@Ganado,

I get that "change the address stored after delete" with VS 2022 Community, the latest update. v17.2.6. Debug and Release mode, x64 or x86.

I've never noticed this behavior before, I normally don't muck around with a deleted pointer except to get a new memory address.

I do get a C6001 warning from Intellisense, uninitialized memory 'foo', with the output statement after deleting foo. That's something positive at least.

I would seriously suggest adding foo = nullptr; after the delete. Then the 2nd foo printout shows it as 00000000000000000 and foo is indeed null.
000001E535020CB0
0000000000000000
foo is null

Still get the C6001 warning with the 2nd foo output.

Let's observe how a smart pointer, std::unique_ptr<> for instance, acts and reacts.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <memory>

class Foo {};

int main()
{
   std::unique_ptr<Foo> foo = std::make_unique<Foo>(Foo());

   std::cout << foo << '\n';

   foo.release();

   std::cout << foo << '\n';

   if (foo != nullptr)
      std::cout << "foo is not null\n";
   else
      std::cout << "foo is null\n";
}
000001C544D11120
0000000000000000
foo is null

No C6001 warning from Intellisense about uninitialized memory, etc. And lo! The smart pointer can't be deleted, only have the contained object released. The smart pointer self-deletes when it goes out of scope. Huzzah!

This is yet another reason why using a smart pointer instead of a raw pointer is a better C++ option.
Last edited on
I can understand a deleted pointer being set to nullptr shows an address of 000000, but I wonder what address 000000008123 is.

M'ok, I'm done wondering. I chalk it up to being some weirdo MS C++ implementation shtick with raw pointers. Smart Pointers! Smart Pointers! Smart Pointers!
I dug a bit deeper to confirm. Yes, it's MS using 0x8123 as a special sanitization value.

https://stackoverflow.com/questions/33374483/what-does-visual-studio-do-with-a-deleted-pointer-and-why
...checks for NULL are a common code construct meaning that an existing check for NULL combined with using NULL as a sanitization value could fortuitously hide a genuine memory safety issue whose root cause really does needs addressing.

For this reason we have chosen 0x8123 as a sanitization value – from an operating system perspective this is in the same memory page as the zero address (NULL), but an access violation at 0x8123 will better stand out to the developer as needing more detailed attention.
MS actually making something to make it easier for developers to debug?!? Wowsers! :Þ

The MS blog post referenced in that SO thread is long and involved -- hey, it's MS! -- but something stood out for me: "Security Development Lifecycle (SDL)"

SDL is default turned on with a new C/C++ project, turn it off at Configuration Properties -> C/C++ -> General -> SDL checks and now there is no sanitizing of pointer memory addresses.

Do I need to mention the MS blog post is from April 2012, so the memory address sanitizing was likely being done before C++11 came along with smart pointers.

Yeah, it became mandatory at MS in 2004, so definitely predates C++11.

Aaaaaand for those interested in what SDL is all about: https://www.microsoft.com/en-us/securityengineering/sdl/
Last edited on
@kigar64551 just to reiterate, pointer is not an address. It is variable. They are different things. A variable has an address. A pointer is a variable that has an address, and points to another variable.
This sounds basic, but it is important to use the right terms.

Here is an in depth video if you are still not convinced. You get the definition at 1:05

https://www.youtube.com/watch?v=iChalAKXffs
Last edited on
Well, DAYUM! Someone sure is getting report happy.....


XD, I was reported too! Is there a rule in this forum saying that you get reported if you metion that some else was reported without cause?
Indeed someone is going around getting report happy.
Well, rudiHammad, do you now understand why in default Visual Studio C++ programs deleting a pointer without assigning nullptr right after changes the pointed-to address? SDL!

BTW, you should consider looking at avoiding raw pointers if possible. And what smart pointers bring to the C++ toolbox, they make memory management insanely easy.

https://www.internalpointers.com/post/beginner-s-look-smart-pointers-modern-c

https://www.internalpointers.com/post/move-smart-pointers-and-out-functions-modern-c

Smart pointers are not a "Praise The Programming Deities" fail-safe panacea so you won't ever make a mistake; but you really, really, REALLY have to work and work hard at doing it.
Yes, thanks George P, it is clear.
Yes, I am actually reading a great book by Scott Meyers, Effective modern C++ where he explains about smart pointers, unique pointers, shared pointers...
There is definetly a lot to learn, thanks for the links.

@kigar64551 just to reiterate, pointer is not an address. It is variable. They are different things. A variable has an address. A pointer is a variable that has an address, and points to another variable.

In a lot of (beginners) tutorials they do not make a distinction between a pointer (memory address) and a variable that has pointer type and therefore "stores" a pointer (memory address). That's a simplification which often is perfectly fine for what they are trying to explain. Still, you do not necessarily have to store a pointer (memory address) in a pointer variable for it to come into existence or for it to be a pointer. Just like an integer value does not necessarily have to be stored in an integer variable to exist or to be an integer 🙂

Quotes from the official documentation:
Address-of operator &
The unary address-of operator (&) returns the address of (that is, a pointer to) its operand.
- https://docs.microsoft.com/en-us/cpp/cpp/address-of-operator-amp?view=msvc-170

new operator (C++)
Attempts to allocate and initialize an object or array of objects of a specified or placeholder type, and returns a suitably typed, nonzero pointer to the object
- https://docs.microsoft.com/en-us/cpp/cpp/new-operator-cpp?view=msvc-170

void* malloc (size_t size);
Allocates a block of size bytes of memory, returning a pointer to the beginning of the block.
- https://cplusplus.com/reference/cstdlib/malloc/

Neither of the above operators/functions forces you to store the returned pointer (address) in a variable 😉

Proof that we can print the pointer to an object without that pointer being stored in a variable:
1
2
3
4
5
6
7
void test(void)
{
    Foo foo;
    printf("Pointer to local object 'foo': %p", &foo);
    printf("Pointer to new heap-allocated object: %p", new Foo());
    printf("Pointer to heap-allocated memory block: %p", malloc(666));
}

Proof that we can access an object without its pointer (as return by new operator) being stored in a variable:
1
2
3
4
void test(void)
{
    (new Foo())->do_something(); // <-- note: this is a memory leak, but still possible
}


________

As an aside: In some CPU architectures, a "pointer" is not as simple as a memory address. For example, some CPU architectures use segmented memory, where a "far" pointer consists of a segment selector plus an offset (within that segment), whereas a "near" pointer consists only of an offset (within the "default" segment). That is why using the term "memory address" is not fully correct when actually a "pointer" is meant.

So, a "pointer" does not only exists when it is stored in a variable. A "pointer" is the thing that we can (but don't have to) store in a pointer variable. And, if we do store a pointer in a pointer variable, then what actually gets stored there (i.e. the pointer) may not be as simple as a memory address (cf. segmented memory).

(I will use "memory address" and "pointer" synonymously, more or less, for reasons of simplicity)

________

Regarding the original question:

It's kind of weird that, apparently, in the latest versions of MSVC, the delete operator actually tries to modify the given pointer variable and overwrites it with some "sentinel" value.

Clearly, that can only be possible, if the pointer is passed to delete as a modifiable "lvalue". There are many cases where delete definitely can not modify the pointer value, simply because the pointer is not given as a variable that could be modified! And, even if we do pass the pointer to delete as a variable (modifiable "lvalue"), then other copies of the pointer may exist elsewhere. So I doubt the usefulness of this "feature".

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void test(void)
{
    Foo *foo = new Foo();
    Foo *bar = foo;
    
    printf("foo: %p\n",   foo);
    printf("bar: %p\n\n", bar);

    delete foo;

    printf("foo: %p\n",   foo);
    printf("bar: %p\n\n", bar);

    bar->do_something(); // <-- whooops !!!
}

foo: 0000027322CF7C70
bar: 0000027322CF7C70

foo: 0000000000008123
bar: 0000027322CF7C70


1
2
3
4
5
6
7
8
void test2(void)
{
    Foo* foo = new Foo();
    printf("foo: %p\n", foo);
    delete (foo + 0);
    printf("foo: %p\n", foo);
    foo->do_something(); // <-- whooops !!!
}

foo: 000001FE134C7B60
foo: 000001FE134C7B60
Last edited on
Yes, I am actually reading a great book by Scott Meyers, Effective modern C++ where he explains about smart pointers, unique pointers, shared pointers...


Note that there has been several significant erratas issued. See:
https://www.oreilly.com/catalog/errata.csp?isbn=0636920033707
Pages: 12