JTC1/SC22/WG21
N0803
Accredited Standards Committee X3 Doc No: X3J16/95-0203 WG21/N0803
Information Processing Systems Date: November 3, 1995
Operating under the procedures of Project: Programming Language C++
American National Standards Institute Ref Doc:
Reply to: Josee Lajoie
(josee@vnet.ibm.com)
Memory Model Issues and Proposed Resolutions
============================================
UK 382 - what is an 'unusable value'?
3.7.3.2[basic.stc.dynamic.allocation] paragraph 4 says:
"A deallocation function can free the storage referenced by the
pointer given as its argument and renders the pointer invalid.
The storage can be made available for further allocation. An
invalid pointer contains an unusable value: it cannot even be used
in an expression."
What is an "unusable value"? What is the runtime behavior of
accessing such an "unusable value"? Suggest that status of pointer
become indeterminate, see paragraph 5.
Proposed resolution:
Combine paragraph 4 and 5.
"If the argument given to a deallocation function is a pointer that
is not the null pointer constant, the deallocation function will
deallocate the storage referenced by the pointer and render the
pointer invalid. The value of a pointer that refers to deallocated
storage is indeterminate. The effect of dereferencing a pointer to
deallocated storage is undefined."
5.3.5[expr.delete] paragraph 4 says:
"If the expression denoting the object in a delete-expression is a
modifiable lvalue, any attempt to access its value after the
deletion is undefined."
Should 3.7.3.2 also say:
"Any attempt to access the value of a pointer that points to
deallocated storage is undefined."
as 5.3.5 seems to indicate?
UK 388 - the term 'valid storage' needs to be defined
3.8[basic.life] paragraph 6:
"After the lifetime of an object has ended and while the storage
which the object occupied still exists, any pointer to the
original object can be used in limited ways. Such a pointer still
points at valid storage and using the pointer as a pointer to the
storage where the object was located, as if the pointer were of
type void*, is well-defined."
In the second sentence, the term "valid storage" is not defined.
A definition needs to be supplied.
Proposed resolution:
Use the terms "allocated storage" (and refer to 3.7.3.2) instead
of using the terms "valid storage".
554 - Can the storage in which a const object resides be reused?
Editorial box 16:
Can the storage in which a const object resides be reused?
And if so, how is the "write-protected" attribute that may be
associated with the memory in which the object resides interpreted?
Because implementations can put const objects with constructors and
destructors in write-protected memory if they can figure out that their
constructors and destructors do not modify the objects, the standard
should not assume that it is the destructor's responsibility to render
the memory "writeable".
Proposed Resolution:
Add the following as paragraph 9 of 3.8[basic.life].
"Creating a new object at the storage location which a const object
occupies or at the storage location which a const object used to
occupy before its lifetime ended results in undefined behavior.
[Example:
struct B {
~B();
};
void h() {
const B b;
b.~B();
new (&b) const B; // undefined behavior
}
--end of example]
"
UK 611 - copying objects via char/unsigned char: Why is memcpy a special
case?
3.9[basic.types] paragraph 2 says:
"For any object type T, the underlying bytes (1.5) of the object
can be copied (using memcpy library function (17.3.1.2)) into an
array of char or unsigned char. This copy operation is
well-defined, even if the object does not hold a valid value of
type T. Whether or not the value of the object is later changed,
if the content of the array of char or unsigned char is copied back
into the object using the memcpy library function, the object shall
subsequently hold its original value."
Why is memcpy a special case? Re-word to allow copying via any
mechanism. Note that the current wording will give one of the few
instances where memcpy is more 'reliable' than memmove. For example:
class Foo { unsigned char a, b; };
struct X {
unsigned char padding;
unsigned char buffer[sizeof(Foo)*2];
};
union Bar {
X x;
Foo foo;
};
...
Bar b;
memcpy( b.x.buffer, &b.foo, sizeof( Foo ) );
...
memcpy( &b.x.foo, b.x.buffer, sizeof( Foo ) );
Note that this example can be constructed regardless of the direction
in which memcpy goes about its buisness.
Proposed Resolution:
The WP should say that as long as the standard library "mem..."
functions are used for the copying then the object will hold its
original value.
557a - What does it mean for a copy operation to be ``well-defined''?
ANSI public comment T25 (3.9-2):
3.9[basic.types] paragraph 2 says:
"For any object of type T, the underlying bytes of the object can
be copied (using the memcpy library function) into an array of
char or unsigned char. This copy operation is well-defined, even
if the object does not hold a valid value of type T."
How can I tell that the ``copy operation is well-defined''?
It is not clear what ``well-defined'' means here or if I can test
for it.
Proposed Resolution:
Reword the paragraph above to remove the use of "well-defined copy
operation":
"For any object of type T, whether or not the object holds a valid
value of type T, the underlying bytes of the object can be copied
(using <TBD> library functions) into an array of char or unsigned
char. If the content of the array of char or unsigned char is
copied back into the object using a memory library function, the
object shall subsequently hold its original value."
557b - Is the notion of value-representation really needed?
ANSI public comment T25 (3.9-4):
3.9[basic.types] paragraph 4 says:
"The value representation of an object is the sequence of bits in
the object representation that hold the value of type T. The
bits of the value representation determine a value, which is one
discrete element of an implementation-defined set of values."
The ``value'' of an object of type T is not necessarily based upon
its bit represention, especially when the class is a handle to other
data. The ``value'' in this case would depend upon how the "=="
operator is overloaded. Even if its ``representation value'' is
somehow defined, what purpose does it serve? Where else is this used
in the draft?
Proposed Resolution:
The concept of "value representation" is needed for describing the
memory model and the guarantees an implementation must provide when
the fundamental types are used to manipulate memory.
Rework the sentences above to say:
"The value representation of an object of type T is the
sequence of bits that holds the value of type T. For POD types,
the value representation is a sequence of bits in the object
representation that determines a value that is one discrete
element of an implementation-defined set of values."
471 - When can an implementation change the value of a delete
expression?
ISSUE 1:
5.3.5[expr.delete] paragraph 4:
"If the expression denoting the object in a delete-expression is a
modifiable lvalue, any attempt to access its value after the
deletion is undefined."
When can an implementation change the pointer value of a delete
expression?
inline void* operator new(void* p) { return p; }
struct Base { virtual ~Base() {} };
struct Immortal : Base {
operator delete(void* p) { new(p) Immortal; }
};
main()
{
Base* bp = new Immortal;
delete bp;
delete bp;
delete bp;
}
Is the above well-formed?
Proposed resolution:
The sentence above is true only if the implementation deallocation
function is called. It doesn't hold if a user deallocation function
is used.
ISSUE 2:
int *a, *b, *c;
a = b = new int;
delete a;
c = b; //1
c = a; //2
because the sentence above discusses lvalues, it implies that
//1 is OK but //2 is undefined !
Proposed resolution:
Use the same wording as those proposed for issue UK 382:
"The value of a pointer that refers to deallocated storage is
indeterminate."
ISSUE 3:
Also, the first sentence of this paragraph says:
"It is unspecified whether the deletion of an object changes its
value."
After deletion has completed, there is no object anymore...
So saying that the value of the object may have changed as the
result of the deletion is somewhat useless.
Proposed Resolution:
This sentence repeats what is already specified in 3.8[basic.life]
and should be deleted from 5.3.5.
So 5.3.5 paragraph 4 should become:
"If the delete-expression calls the implementation deallocation
function, and if the operand of the delete expression is not the
null pointer constant, the deallocation function will deallocate the
storage referenced by the pointer and render the pointer invalid.
The value of a pointer that refers to deallocated storage is
indeterminate."
93 - Deleting the "current object" (this) in a member function
In a standard conforming program, may delete be used within a
non-static member function (or within a function which is called
directly or indirectly by such a function) to delete the object for
which the non-static member function was called?
Example:
struct S { void member (); };
void delete_S (S *arg) { delete arg; }
void S::member ()
{
delete_S (this);
}
If this is prohibited in a standard conforming program is a standard
conforming implementation required to issue a diagnostic for such
code?
Proposed Resolution:
The proposed resolutions for UK issue 382 and for issue 472 already
handle this. [i.e. the effect of dereferencing a pointer to
deallocated storage is undefined.]
416 - Can a delete expression be of abstract type?
I believe that it should be ok for a pointer to abstract type to
be the operand of a delete expression, as long as the abstract class
type has a virtual destructor.
Proposed Resolution:
The type of the cast-expression can be an abstract class type
provided that the abstract class was previously defined to have a
virtual destructors.
417 - Should pointer arithmetic be allowed for pointer-to-abstract
classes?
Should pointer arithmetic and/or the sizeof operator be allowed for
operands whose type is some pointer-to-abstract type?
WP sections that are affected:
5.2.1 subscripting ([]):
5.2.5 (post) increment + decrement
5.3.2 (pre) increment + decrement
5.7 additive
5.17 assignment (+= -=)
No,
an operand of pointer to abstract class type should not be
allowed. This seems to make sense since these operators do not
allow operands of pointer to incomplete class type.
5.3.1 indirection
5.9 relational
5.10 equality
5.17 assignment (simple =)
yes,
an operand of pointer to abstract class type should be allowed.
allowed. This seems to make sense since these operators allow
operands of pointer to incomplete class type.
597 - Should the results of the + or - operators be well-defined if one
operand is a null pointer and the other is the value 0?
The WP does not currently say what the result of the following
operations is:
(char*)0 + 0 == (char*)0
(char*)0 - (char*)0 == 0
[Andrew Koenig, core-6234:]
"Without this, it is impossible to use the pair (0,0) to indicate
an empty range, for example. Moreover, I suspect that otherwise
null pointers fail to meet the requirements for random access
iterators.
Consider the following loop, where p and q are pointers:
while (p < q) {
f(*p);
++p;
}
One would think it possible to rewrite it as follows:
int n = q - p;
for (int i = 0; i < n; ++i)
f(p[i]);
but this plausible rewrite fails if p and q are both zero?"
Proposed resolution:
5.7[expr.add] paragraph 5 should say:
"If the null pointer constant value is added to a pointer value,
the result is the original pointer value."
5.7[expr.add] paragraph 6 should say:
"If the null pointer constant value is substracted from a pointer
value, the result is the original pointer value. If a pointer
value is substracted from the same pointer value, the result is
the null pointer constant value."
596 - What is the result of a relational operator if only one operand
is a null pointer?
5.9[expr.rel] paragraph 2 says:
"If two pointers to the same type point to different objects or
functions, or only one of them is null, they shall compare unequal."
Given:
(char*)0 >= p // 1
(char*)0 < p // 2
If p is not a null pointer, what is the result of //1 and //2 if p
shall compare unequal with a null pointer? Is it undefined? Or
well-defined and false?
paragraph 2 also says:
"If two pointers to the same type point to the same object or
function or ... are both null, they compare equal."
(char*)0 >= (char*)0 //3
(char*)0 < (char*)0 //4
The current wording indicates that //3 is well-defined and true.
What about //4? Is it undefined or well-defined and false?
Proposed Resolution:
Rework the list in 5.9 paragraph 2 as follows:
"-- If two pointers p1 and p2 of the same type point to the same
object or function, or both point one past the end of the same
array, or are both null, they compare equal and the result of
'p1 <= p2' or 'p1 >= p2' is true while the result of 'p1 < p2'
or 'p1 > p2' is false.
-- If two pointers p1 and p2 of the same type point to different
objects or functions, or only one of them is null, they compare
unequal and the result of 'p1 <= p2', 'p1 >= p2', 'p1 < p2' or
'p1 > p2' is unspecified.
-- ..."
513 - Are pointer comparisons implementation-defined, unspecified or
undefined?
5.9p2 last '--' says:
"Other pointer comparisons are implementation-defined."
Comparison of unrelated pointers should be unspecified or
undefined. At present it reads implementation defined, but I doubt
that the exact rules can be described by a compiler vendor.
unspecified behavior is: behavior for a correct program construct
and correct data, that depends on the implementation. The
implementation is not required to document which behavior occurs.
undefined behavior is: Behavior, such as might arise upon use of an
erroneous program construct or of erroneous data, for which the
standard imposes no requirements. Permissible undefined behavior
ranges from ignoring the situation completely with unpredictable
results, to behaving during translation or program execution in a
documented manner characteristic of the environment (with or
without the issuance of a diagnostic message), to terminating a
translation or execution (with the issuance of a diagnostic
message).
Given that "undefined behavior arise upon use of an erroneous program
construct or of erroneous data", it seems that the pointer comparisons
not covered by the standard should be undefined.
Proposed Resolution:
Change the text above to say:
"Other pointer comparisons are undefined."
476 - Can objects with "indeterminate initial value" be referred to?
8.5p6 says:
"If no initializer is specified for an object with automatic or
dynamic storage duration, the object and its subobjects, if any,
have an indeterminate initial value."
The C standard specifies that accessing a variable with
indeterminate value results in undefined behavior, but the C++ draft
contains no such language.
Proposed Resolution:
Add the following text at the end of 8.5 paragraph 6:
"Referring to an object with an indeterminate value results in
undefined behavior."