ISO/ IEC 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."