Paul said:
Goran said:
int (*p1)[5] = (int (*)[5])new int*[5]; /*2d array*/
int (*p2)[5] = (int (*)[5])new int[5]; /*1d array*/
int (*p3)[5] = new int[5][5]; /*2d array*/
delete[] p1;
delete[] p2;
delete[] p3;
What is the difference with the above deletes?
As far as I am concerned, difference is: first two are UB (platform-
specific at best), because of wrong casts in corresponding "new",
whereas the third one is correct.
Why are they UB? After having a read through the rellevant pages of the
standard I don't think that it is UB.
We shall see.
The standard specifically states that the pointer returned from new must
be castable.
Correct: [5.2.10/7] states:
A pointer to an object can be explicitly converted to a pointer to an
object of different type.65) Except that converting an rvalue of type
"pointer to T1" to the type "pointer to T2" (where T1 and T2 are object
types and where the alignment requirements of T2 are no stricter than
those of T1) and back to its original type yields the original pointer
value, the result of such a pointer conversion is unspecified.
That implies that after casting, you have an unspecified result. The only
thing you can do with it, is casting back, and even that presupposes
compatible alignment requirement.
In particular, passing the unspecified value obtained by casting to delete
or delete [] is undefined behavior.
Consider this more complex example:
class Base{
public:
Base() {std::cout<<"Constructing base\n";}
virtual ~Base(){std::cout<<"Destructing base\n";}
};
class Derived: public Base{
public:
Derived() {std::cout<<"Constructing derived\n";}
~Derived(){std::cout<<"Destructing derived\n";}
};
int main() {
Base (*p2)[3] = (Base (*)[3])new Derived[2][3];
delete[] p2;
}
The cast is required , otherwise you'd need to loop through the array
columns for allocation and deallocation..
I could cast it back prior to deletion with:
delete[] (Derived (*)[3])p2;
But this is unneccessary because Base type is an acceptable pointer type.
What do you mean by "acceptable"?
Also, the value of the variable p2 is _unspecified_ as it is obtained
through casting.
The type is only neccessay for the correct destruction of objects. For
example:
Base *p2 = new Derived[3];
delete[] p2;
is identical to
Base (*p2)[3] = (Base (*)[3])new Derived[3];
deletep[] p2;
The type that is relevant here is that it is Base or Derived, the number
of elements destructed is decided by the C runtime. As long as the
dereferenced pointer passed to delete has the same value as the pointer
returned by new all is ok, because it is converted to void* anyway. The C
runtime decides whether it deletes 3 objects or 1 , not the type system.
Sameness of value is not guaranteed by the standard (pretending we know what
it means). Consider:
int main () {
Derived (*p1) [3] = new Derived [2][3];
Base (*p2) [3] = (Base (*)[3]) p1;
assert( p1 == p2 );
}
This does not even compile as p1 and p2 are of different type.
If int[3] and int* both point to the same address, then converted to void*
they are identical.
So this is what we mean by "sameness of values". Ok, let's consider
int main () {
Derived (*p1) [3] = new Derived [2][3];
Base (*p2) [3] = (Base (*)[3]) p1;
assert( (void*)p1 == (void*)p2 );
}
Now, the code compiles. However, contrary to expectations, the assert is
allowed to _fail_ because the value of p2 is unspecified. In fact, using the
unspecified value as an argument for a cast is UB. So, we could do:
int main () {
Derived (*p1) [3] = new Derived [2][3];
Base (*p2) [3] = (Base (*)[3]) p1;
assert( p2 == (Base (*) [3]) p1 );
}
This looks as though it could not fail. However, the two sides of the == are
both unspecified values. It is neither clear that they are equal nor that
comparing them does not result in a core dump.
If you see more guarantees in the standard that would render any of the
above defined behavior, please cite them.
[...]
Best,
Kai-Uwe Bux