Article View: comp.std.c++
Article #3517Re: operators new[]/delete[]
From: Fergus Henderson
Date: Tue, 27 Feb 1996 07:29
Date: Tue, 27 Feb 1996 07:29
229 lines
10904 bytes
10904 bytes
"Constantine Antonovich:" <const@Orbotech.Co.IL> writes:
> 2. Undefined behavior.
>
> Of course, some constructions in a possible program may
>produce undefined behavior. Nevertheless, even undefined behavior
>should have some definition.
I disagree. Imposing restrictions on the behaviour of code
which has "undefined behaviour" would be a very confusing use
of terminology, and more importantly would place constraints on
implementors that would prevent efficient implementations.
You may perhaps be able to make a case that certain specific cases
of undefined behaviour ought to be instead made merely unspecified.
But in the general case, a write via a stray pointer might cause
arbitrary instructions to be executed, thus violating any guarantees.
There is basically no way that an implementation which allows writes
via stray pointers can prevent this. Preventing stray pointer writes
is possible, but would have a significant efficiency penalty.
>Let's consider the following code:
>
>//----------------------------------------------------------
> A* ap=new A;
> B* bp=reinterpret_cast<B*>(ap);
> delete bp; // #here
>//----------------------------------------------------------
>
>Without any doubt, result of the code executed in "#here" line is
>undefined. But definitely I wouldn't like, as a result of
>uncertainty of the behavior, my compiler to send email complaint
>to some League "C++ compilers against stupidity of the
>programmers".
You might not like it, but depending on the types `A' and `B',
this could cause heap corruption on some implementations.
For example, the compiler might represent pointers to `A' as
pointing not directly to the start of the memory for `A', but
instead pointing to some fixed offset before or after the start.
The reinterpret_case<B*> might not adjust for that offset.
Heap corruption could cause writes through stray pointers,
which could cause arbitrary code to be executed. The result
of that could be anything -- sending email complaints is
unlikely, but can't be ruled out.
>Also I wouldn't like my compiler to recognize
>incorrectness of the code and silently to call A destructor (after
>all, bp points to A object, isn't it?) instead of B one. Here I
>mean that
>
> UNDEFINED BEHAVIOR HAS AN ERROR MEANING.
> UNDEFINED BEHAVIOR ALWAYS RESULTING IN CORRECT EXECUTION OF
> A CODE WITH UNDEFINED BEHAVIOR, IS FORBIDDEN.
Preventing implementations from doing "the right thing" when
executing code with undefined behaviour would place unreasonable
constraints on implementors that would prevent efficient implementations.
For example, a write via a stray pointer might write to some ununsed
memory, in which case it will have no effect, and the code may continue
to work. There is basically no way the implementation can avoid this
other than by checking for stray pointer writes, which as I said before
would have a significant efficient impact.
> 4. Alignment and memory allocation.
>
> In the starting the article example, the following code
>
>//----------------------------------------------------------
> assert(sizeof(A)==sizeof(B));
>
> B* bptr=new B[size];
> A* aptr=(A*)bptr;
>//----------------------------------------------------------
>
>really seems dangerous.
>
> Fergus Henderson writes:
> "This assertion is not guaranteed to succeed.
> It would take an extremely perverse implementation
> for it to fail, however, so I think it would be very
> portable, even though it is not strictly guaranteed
> to work."
>
>This sentence seems to be reasonable but, in deal, this assertion
>guarantees the correctness ALMOST ALWAYS and under that
>circumstance this check is absolutely portable.
I don't understand what you are saying here. (This assertion guarantees
the correctness of what? Under which circumstances?)
> An implementation hasn't to be extremely perverse the
>assertion condition to fail. It can be very simple one where B
>class for example has its own operator new[] allocating memory in
>specific alignment suitable for B but not for any other class and
>A one particularly (and even that is impossible for compilers
>still not supporting overloading of operator new[]).
In the test case, A and B were both identical classes (other than the
class name); I think only a peverse implementation would allocate them
different sizes.
Your talk about B having `operator new[]' is describing a hypothetical
peice of source code, not a hypothetical C++ implementation; I don't
see how it is relevant.
> Fergus Henderson continues:
>//----------------------------------------------------------
> B* bptr=new B[size];
> A* aptr=(A*)bptr;
>//----------------------------------------------------------
> "This cast has unspecified behavior. (See 5.2.9
> [expr.cast.reinterpret]/8.). However, I would
> expect it to work on most implementations."
>
> This article of ANSI draft interprets the operation as
>unspecified in case of cast from T1 to T2 and back and if there is
>difference in alignment of T1 and T2. Obviously, that is not our
>case (at least because definition of allocation function returning
>suitable for any object alignment).
That's irrelevant, since in your example piece of code, you don't cast
back to `B *'. 5.2.9/8 says that the result in this case is unspecified.
> Fergus Henderson agrees with Steve Clamage:
> "This has undefined behaviour. It contravenes 5.3.5
> [expr.delete]/2, which says that the expression
> passed to `delete []' must be a pointer to the first
> element of an array of objects allocated with `new []';
> this is not the case, because although there once was
> such an array at that memory location, its lifetime
> ended when the memory was overwritten by the calls
> to placement new (see 3.8[basic.life]/1)."
>
> There is at least one self-contradictory point in that
>conclusion. Of course, lifetime of all B objects had been ended,
>by why does that mean end of the array life?
The lifetime of an array object is distinct from the lifetime of its
elements. The ending of the lifetime of all the B objects (which you
did by explciitly calling the destructur) doesn't end the lifetime of
the array.
What ends the lifetime of the array is reusing the memory (which
you did by calling placement new). 3.8 is quite clear about this:
"the lifetime of an array object ... ends when the storage which the array ...
occupies is reused or released."
>Or in contrary, if
>end of life-time of B objects means end of life-time of the array,
>so creation of A objects should mean creation of new array,
>shouldn't it?.
Yes. But this array was not "created with new []" as required by
5.3.5.2, which you quite below, and thus the behaviour is still
undefined.
> Article 5.3.5.2 of ANSI draft says something slightly
> different:
> "...In the second alternative (delete array), the value
> of the operand of delete shall be a pointer to an
> array created by a new-expression without a new-placement
> specification. If not, the behavior is undefined."
>
>
> So delete takes as its argument POINTER TO ARRAY (even objects
>are not mentioned).
This is indeed a minor error; it has been changed in the January 96
draft to say "... shall be a pointer to the first element of an array ...".
>No one says that
> ...pointer passed to delete[] must match the type
> of the pointer returned by new[]...
> ...the expression passed to `delete []' must be a
> pointer to the first element of an array of objects
> allocated with `new []'...
It does say that the dynamic type of the pointer passed to delete[] must
match its static type, which is effectively the same thing.
(See 5.3.5/3.)
> And here we really arrive to the final point. ANSI draft gives
>no strong array definition to disable ambiguous array
>interpretation. And above-mentioned common-sense based array
>understanding has all rights to exist.
The draft is definitely not easy reading, but I think it does define
things sufficiently well to allow unambiguous interpretation in this
case.
> Addition to array definition [dcl.array]:
> Array of N T object represents contiguous amount of
> memory of suitable size and alignment with N
> non-overlapping objects of type T placed into the
> memory with no gaps and each properly aligned.
I think this is already covered by the wording on array lifetimes (3.8)
and the statement in [dcl.array] that an array object consists of
contiguously allocated elements. But adding that wording might make
things clearer.
> Addition to operator delete [expr.delete] ("above" here
> means all previously said by the standard):
> In either alternative, the type of the deleted object
> is evaluated as described above and according to the
> type of the actual operand.
The draft says that if the dynamic type is different from the static
type, then the behaviour is undefined. So the above text would be
a change, not just a clarification. I'm not at all convinced that
it would be a change for the better. Leaving the behaviour
undefined in this case gives implementors more flexibility, and
I think that flexibility is probably more important than defining
the behaviour of programs like yours which play tricks with memory
allocation.
--
Fergus Henderson WWW: http://www.cs.mu.oz.au/~fjh
fjh@cs.mu.oz.au PGP: finger fjh@128.250.37.3
---
[ To submit articles: try just posting with your news-reader.
If that fails, use mailto:std-c++@ncar.ucar.edu
FAQ: http://reality.sgi.com/employees/austern_mti/std-c++/faq.html
Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html
Comments? mailto:std-c++-request@ncar.ucar.edu.
]
Technical Details
Message-ID:
<4gu414$62u@mulga.cs.mu.OZ.AU>Path:
arc1-ca.pugleaf.net!in1.uu.net!bounce-backReferences:
<313166CF.11C2@orbotech.co.il>Show Full Headers
Complete Article Headers
From: fjh@munta.cs.mu.OZ.AU (Fergus Henderson) Message-ID: <4gu414$62u@mulga.cs.mu.OZ.AU> X-Original-Date: 27 Feb 1996 05:15:16 GMT Path: arc1-ca.pugleaf.net!in1.uu.net!bounce-back Date: 27 Feb 96 07:29:29 GMT Approved: fjh@cs.mu.oz.au Newsgroups: comp.std.c++ Subject: Re: operators new[]/delete[] Organization: Comp Sci, University of Melbourne References: <313166CF.11C2@orbotech.co.il> X-Auth: PGPMoose V1.1 PGP comp.std.c++ iQBFAgUBMTKy6OEDnX0m9pzZAQEo1AF/Q5+at62o6RsaOD2qkQtPJ4DaGReJKRe5 sM7uha7CwyYuFBTzxJuuQkXS8J4V5mG1 =igrN