I ran into my first compiler that changes the lvalue passed to ::delete, but doesn't zero out the lvalue. That is the following is true:
Foo * p = new Foo();
Foo * q = p;
assert(p != 0);
assert(p == q);
::delete p;
assert(p != q);
assert(p != 0);
Note that p is not zero after the delete operation, and it has changed from it's old value. A coworker told me that this is not unusual in his experience having worked with some mainframe C++ compilers that would change p to 0xFFFFFFFF, as well as other compilers that would change p to 0.
Where in the C++ Standard does it say that a compiler is allowed to do this?
Searching through StackOverflow, I found this question: Why doesn’t delete set the pointer to NULL? which had an answer that referred to Bjarne Stroustrup's response that includes the statement:
C++ explicitly allows an implementation of delete to zero out an lvalue operand, and I had hoped that implementations would do that, but that idea doesn't seem to have become popular with implementers.
I've read and re-read section 5.3.5 and 12.5 of the final committee draft C++0x standard, but I'm not seeing the "explicit" part. Am I just looking in the wrong sections of the standard? Or is there a chain of logic that is in the sections but I'm just not connecting together properly.
I don't have my copy of the Annotated C++ Reference Manual anymore. Was it in the ARM that a compiler could do this?
[Edit: Correcting the section reference from 3.5.3 to 5.3.5. I'm also adding an interesting paradox as a counterpoint to Henk's assertion that p is undefined after delete.]
There is an interesting paradox if p is initialized to null.
Foo * p = 0;
Foo * q = p;
assert(p == 0);
assert(p == q);
::delete p;
assert(p == q);
assert(p == 0);
In this case though, the behavior is well documented. When delete gets a null pointer, it is suppose to do nothing, so p remains unchanged.
It might not be so explicit. In 5.3.5/7 it says that the delete expression will call a deallocator function. Then in 3.7.3.2/4 it says that using a pointer that has been deallocated is undefined. Since the value of the pointer cannot be used after the deallocation, then whether the pointer keeps the value or the value is changed by the implementation does not make a difference.
5.3.5/7
The delete-expression will call a deallocation function (3.7.3.2).
3.7.3.2/4
If the argument given to a deallocation function in the standard library is a pointer that is not the null pointer value (4.10), the deallocation function shall deallocate the storage referenced by the pointer, render- ing invalid all pointers referring to any part of the deallocated storage. The effect of using an invalid pointer value (including passing it to a deallocation function) is undefined.
The references are from the current standard. In the upcoming standard 5.3.5/7 has been reworded:
C++0x FD 5.3.5/7
If the value of the operand of the delete-expression is not a null pointer value, the delete-expression will call a deallocation function (3.7.4.2). Otherwise, it is unspecified whether the deallocation function will be called. [ Note: The deallocation function is called regardless of whether the destructor for the object or some element of the array throws an exception. — end note ]
Related
When implementing my own unique_ptr( just for fun), I found it cannot pass this test file from libstdcxx:
struct A;
struct B
{
std::unique_ptr<A> a;
};
struct A
{
B* b;
~A() { VERIFY(b->a != nullptr); }
};
void test01()
{
B b;
b.a.reset(new A);
b.a->b = &b;
}
gcc passes this test file happily (of course, this file is from libstdcxx), while clang fails for the VERIFY part.
Question:
Is it implementation dependent or undefined behavior?
I guess this postcondition (b->a != nullptr) is important for gcc, otherwise it'll not have a test file for it, but I don't know what's behind it. Is it related to optimization? I know many UB are for better optimizations.
clang (libc++) seems to be non-compliant on this point because the standard says:
[unique.ptr.single.dtor]
~unique_ptr();
Requires: The expression get_deleter()(get()) shall be well-formed, shall have well-defined behavior, and shall not throw exceptions.
[ Note: The use of default_delete requires T to be a complete type.
— end note
]
Effects: If get() == nullptr there are no effects.
Otherwise get_deleter()(get()).
So the destructor should be equivalent to get_deleter()(get()), which would imply that b->a cannot be nullptr within the destructor of A (which is called inside get_deleter() by the delete instruction).
On a side note, both clang (libc++) and gcc (libstdc++) sets the pointer to nullptr when destroying a std::unique_ptr, but here is gcc destructor:
auto& __ptr = _M_t._M_ptr();
if (__ptr != nullptr)
get_deleter()(__ptr);
__ptr = pointer();
...and here is clang (call to reset()):
pointer __tmp = __ptr_.first();
__ptr_.first() = pointer();
if (__tmp)
__ptr_.second()(__tmp);
As you can see, gcc first deletes then assigns to nullptr (pointer()) while clang first assigns to nullptr (pointer()) then delete1.
1 pointer is an alias corresponding to Deleter::pointer, if it exists, or simply T*.
Both libstdc++ and libc++ are conforming because this is unobservable by a well-defined program. During the destructor's execution, [res.on.objects]/2 prohibits any attempt to observe (or modify, for that matter) the state of the unique_ptr on pain of undefined behavior:
If an object of a standard library type is accessed, and the beginning of the object's lifetime does not happen before the access, or the access does not happen before the end of the object's lifetime, the behavior is undefined unless otherwise specified.
In fact, unique_ptr's destructor is why this paragraph was added in the first place (by LWG2224).
Additionally, after the destruction is complete, the contents of the storage it occupies is indeterminate by [basic.life]/4:
The properties ascribed to objects and references throughout this document apply for a given object or reference only during its lifetime.
There is no requirement on the final state of the memory occupied by the std::unique_ptr<> after destruction. It wouldn't make sense to set it to null as the memory is being returned to where ever it was allocated from. GCC probably checks that its not null to make sure nobody added unnecessary code to clear it. Under the right circumstances, forcing a clear of the value when not needed could cause a performance hit.
C++03 Standard say's:
5.3.5 Delete
[...] In either alternative, if the value of the operand of delete is the null pointer the operation has no effect.[...]
char *p = nullptr;
delete p; //no effect
It means, it is valid to delete null pointer in c++.
What C++17 standard say about calling delete on nullptr pointer?
Yes it is valid, and it results in a noop. reference
If expression evaluates to a null pointer value, no destructors are called, and the deallocation function is not called.
For destructors, [expr.delete]/6:
If the value of the operand of the delete-expression is not a null pointer value, the delete-expression will invoke the destructor (if any) for the object or the elements of the array being deleted.
This technically doesn't say that if the operand is a null pointer value, the destructor isn't invoked. Probably a minor wording issue?
For deallocation, [expr.delete]/7:
If the value of the operand of the delete-expression is a null pointer value, it is unspecified whether a deallocation function will be called as described above.
Unspecified deallocation, but likely no destruction.
Note also, from [basic.stc.dynamic.deallocation]/3, which clarifies that even if the standard library deallocation function is called in this situation, there is no effect:
The value of the first argument supplied to a deallocation function may be a null pointer value; if so, and if the deallocation function is one supplied in the standard library, the call has no effect.
Let's say we have this legacy code from C++98:
bool expensiveCheck();
struct Foo;
bool someFunc()
{
Foo *ptr = 0;
if( expensiveCheck() )
ptr = new Foo;
// doing something irrelevant here
...
if( ptr ) {
// using foo
}
delete ptr;
return ptr; // here we have UB(Undefined Behavior) in C++11
}
So basically pointer here is used to keep dynamically allocated data and use it as a flag at the same time. For me it is readable code and I believe it is legal C++98 code. Now according to this questions:
Pointers in c++ after delete
What happens to the pointer itself after delete?
this code has UB in C++11. Is it true?
If yes another question comes in mind, I heard that committee puts significant effort not to break existing code in new standard. If I am not mistaken in this case this not true. What is the reason? Is such code considered harmfull already so nobody cares it would be broken? They did not think about consequences? This optimization is so important? Something else?
Your example exhibits undefined behavior under C++98, too. From the C++98 standard:
[basic.stc.dynamic.deallocation]/4 If the argument given to a deallocation function in the standard library is a pointer that is not the null pointer value (4.10), the deallocation function shall deallocate the storage referenced by the pointer, rendering invalid all pointers referring to any part of the deallocated storage. The effect of using an invalid pointer value (including passing it to a deallocation function) is undefined.33)
Footnote 33) On some implementations, it causes a system-generated runtime fault.
After reading many posts about this, I want to clarify the next point:
A* a = new A();
A* b = a;
delete a;
A* c = a; //illegal - I know it (in c++ 11)
A* d = b; //I suppose it's legal, is it true?
So the question is about using the value of copy of deleted pointer.
I've read, that in c++ 11 reading the value of a leads to undefined behaviour - but what about reading the value of b?
Trying to read the value of the pointer (note: this is different to
dereferencing it) causes implementation-defined behaviour since C++14,
which may include generating a runtime fault. (In C++11 it was
undefined behaviour)
What happens to the pointer itself after delete?
Both:
A* c = a;
A* d = b;
are undefined in C++11 and implementation defined in C++14. This is because a and b are both "invalid pointer values" (as they point to deallocated storage space), and "using an invalid pointer value" is either undefined or implementation defined, depending on the C++ version. ("Using" includes "copying the value of").
The relevant section ([basic.stc.dynamic.deallocation]/4) in C++11 reads (emphasis added):
If the argument given to a deallocation function in the standard library is a pointer that is not the null pointer value (4.10), the deallocation function shall deallocate the storage referenced by the pointer, rendering invalid all pointers referring to any part of the deallocated storage. The effect of using an invalid pointer value (including passing it to a deallocation function) is undefined.
with a non-normative note stating:
On some implementations, it causes a system-generated runtime
In C++14 the same section reads:
If the argument given to a deallocation function in the standard library is a pointer that is not the null pointer value (4.10), the deallocation function shall deallocate the storage referenced by the pointer, rendering invalid all pointers referring to any part of the deallocated storage. Indirection through an invalid pointer value and passing an invalid pointer value to a deallocation function have undefined behavior. Any other use of an invalid pointer value has implementation-defined behavior.
with a non-normative note stating:
Some implementations might define that copying an invalid pointer value causes a system-generated runtime fault
These 2 lines do not have any difference (meaning legality for C++):
A* c = a; //illegal - I know it (in c++ 11)
A* d = b; //I suppose it's legal, is it true?
Your mistake (and it is pretty common) to think if you call delete on a it makes it any different than b. You should remember that when you call delete on a pointer you pass argument by value, so memory, where a points to after delete is not usable anymore, but that call does not make a any different than b in your example.
You should not use the pointer after delete. My below example with acessing a is based on implementation-defined behaviour.
(thanks to for M.M and Mankarse for pointing this)
I feel that it is not the variable a (or b, c, d) that is important here, but that the value (=the memory address of a deallocated block) which in some implementations can trigger a runtime fault when used in some 'pointer context'.
This value may be an rvalue/expression, not necessarily the value stored in a variable - so I do not believe the value of a ever changes (I am using the loose 'pointer context' to distinguish from using the same value, i.e. the same set of bits, in non-pointer related expressions - which will not cause a runtime fault).
------------My original post is below.---------------
Well, you are almost there with your experiment. Just add some cout's like here:
class A {};
A* a = new A();
A* b = a;
std::cout << a << std::endl; // <--- added here
delete a;
std::cout << a << std::endl; // <--- added here. Note 'a' can still be used!
A* c = a;
A* d = b;
Calling delete a does not do anything to the variable a. This is just a library call. The library that manages dynamic memory allocation keeps a list of allocated memory blocks and uses the value passed by variable a to mark one of the previously allocated blocks as freed.
While it is true what Mankarse cites from C++ documentation, about: "rendering invalid all pointers referring to any part of the deallocated storage" - note that the value of variable a remains untouched (you did not pass it by reference, but by value !).
So to sum up and to try to answer your question:
Variable a still exists in the scope after delete. The variable a still contains the same value, which is the address of the beginning of the memory block allocated (and now already deallocated) for an object of class A. This value of a technically can be used - you can e.g. print it like in my above example – however it is hard to find a more reasonable use for it than printing/logging the past...
What you should not do is trying to de-reference this value (which you also keep in variables b, c, and d) – as this value is not a valid memory pointer any longer.
You should never rely on the object being in the deallocated storage (while it is quite probable that it will remain there for some while, as C++ does not require to clear the storage freed after use) - you have no guarantees and no safe way to check this).
The default placement new operator is declared in 18.6 [support.dynamic] ¶1 with a non-throwing exception-specification:
void* operator new (std::size_t size, void* ptr) noexcept;
This function does nothing except return ptr; so it is reasonable for it to be noexcept, however according to 5.3.4 [expr.new] ¶15 this means that the compiler must check it doesn't return null before invoking the object's constructor:
-15-
[Note: unless an allocation function is declared with a non-throwing exception-specification (15.4), it indicates failure to allocate storage by throwing a std::bad_alloc exception (Clause 15, 18.6.2.1); it returns a non-null pointer otherwise. If the allocation function is declared with a non-throwing exception-specification, it returns null to indicate failure to allocate storage and a non-null pointer otherwise. —end note] If the allocation function returns null, initialization shall not be done, the deallocation function shall not be called, and the value of the new-expression shall be null.
It seems to me that (specifically for placement new, not in general) this null check is an unfortunate performance hit, albeit small.
I've been debugging some code where placement new was being used in a very performance-sensitive code path to improve the compiler's code generation and the check for null was observed in the assembly. By providing a class-specific placement new overload that is declared with a throwing exception-specification (even though it can't possibly throw) the conditional branch was removed, which also allowed the compiler to generate smaller code for the surrounding inlined functions. The result of saying the placement new function could throw, even though it couldn't, was measurably better code.
So I've been wondering whether the null check is really required for the placement new case. The only way it can return null is if you pass it null. Although it's possible, and apparently legal, to write:
void* ptr = nullptr;
Obj* obj = new (ptr) Obj();
assert( obj == nullptr );
I can't see why that would be useful, I suggest it would be better if the programmer had to check for null explicitly before using placement new e.g.
Obj* obj = ptr ? new (ptr) Obj() : nullptr;
Has anyone ever needed placement new to correctly handle the null pointer case? (i.e. without adding an explicit check that ptr is a valid memory location.)
I'm wondering whether it would be reasonable to forbid passing a null pointer to the default placement new function, and if not whether there is some better way to avoid the unnecessary branch, other than trying to tell the compiler the value is not null e.g.
void* ptr = getAddress();
(void) *(Obj*)ptr; // inform the optimiser that dereferencing pointer is valid
Obj* obj = new (ptr) Obj();
Or:
void* ptr = getAddress();
if (!ptr)
__builtin_unreachable(); // same, but not portable
Obj* obj = new (ptr) Obj();
N.B. This question is intentionally tagged micro-optimisation, I am not suggesting that you go around overloading placement new for all your types to "improve" performance. This effect was noticed in a very specific performance-critical case and based on profiling and measurement.
Update: DR 1748 makes it undefined behaviour to use a null pointer with placement new, so compilers are no longer required to do the check.
While I can't see much of a question in there except "Has anyone ever needed placement new to correctly handle the null pointer case?" (I haven't), I think the case is interesting enough to spill some thoughts on the issue.
I consider the standard broken or incomplete wrt the placement new function and requirements to allocation functions in general.
If you look closely at the quoted §5.3.4,13, it implies that every allocation function has to be checked for a returned nullpointer, even if it is not noexcept. Therefore, it should be rewritten to
If the allocation function is declared with a non-throwing exception-specification and returns null, initialization shall not be done, the deallocation function shall not be called, and the value of the new-expression shall be null.
That would not harm the validity of allocation functions throwing exceptions, since they have to obey §3.7.4.1:
[...] If it is successful, it shall return the address of the start of a block of storage whose length in bytes shall be at least as large as the requested size. [...] The pointer returned shall be suitably aligned so that it can be converted to a pointer of any complete object type with a fundamental alignment requirement (3.11) and then used to access the object or array in the storage allocated (until the storage is explicitly deallocated by a call to a corresponding deallocation function).
And §5.3.4,14:
[ Note: when the allocation function returns a value other than null, it must be a pointer to a block of storage in which space for the object has been reserved. The block of storage is assumed to be appropriately aligned and of the requested size. [...] -end note ]
Obviously, a placement new that just returns the given pointer, cannot reasonably check avilable storage size and alignment. Therefore,
§18.6.1.3,1 about placement new says
[...] The provisions of (3.7.4) do not apply to these reserved placement forms of operator new and operator delete.
(I guess they missed to mention §5.3.4,14 at that place.)
However, together these paragraphs say indirectly "if you pass a garbage pointer to the palcement functions, you get UB, because §5.3.4,14 is violated". So it's up to you to check the sanity of any poitner given to placement new.
In that spirit, and with the rewritten §5.3.4,13, the standard could strip the noexcept from placement new, leading to an addition to that indirect conclusion: "...and if you pass null, you get UB as well". On the other hand, its much less likely to have a misaligned pointer or pointer to too few memory than having a null pointer.
However, this would remove the need for checking against null, and it would fit well to the philosophy "don't pay for what you don't need". The allocation function itself would not need to check, because §18.6.1.3,1 explicitly says so.
To round things up, one could consider adding a second overload
void* operator new(std::size_t size, void* ptr, const std::nothrow_t&) noexcept;
Sadly, proposing this to the committee is unlikely to result in a change, because it would break existing code relying on placement new being ok with null pointers.