ISO/ IEC JTC1/SC22/WG21 N0727


Accredited Standards Committee X3       Doc No: X3J16/95-0127   WG21/N0727
Information Processing Systems          Date:    Jun 30, 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
============================================
 
o new/delete
============
 
 *Issue 463:
  ----------
  3.7.3.1[basic.stc.dynamic.allocation] paragraph 1 says:
    "Allocation functions can be static class member functions or global
     functions."
  3.7.3.2[basic.src.dynamic.deallocation] paragraph 1 says:
    "Like allocation functions, dellocation functions can be static
     class member functions or global functions."
 
  ".. can be ..."
  Is this intended to prevent a program from declaring/defining
  allocation/deallocation functions in a named namespace?
  o If yes, this should be clearer.
  o If no, and if allocation/deallocation functions can be declared in
    a named namespace scope, 5.3.4[expr.new], 5.3.5[expr.delete],
    12.5[class.free] should be clearer and describe how name look up
    proceeds if allocation/deallocation functions can be declared in
    a named namespace scope.
 
  Proposal 463:
  -------------
  My assumption is that a program shall not declare any allocation or
  deallocation functions in a named namespace scope.
  3.7.3.1[basic.stc.dynamic.allocation] paragraph 1 should be changed
  to say:
    "Allocation functions shall be static class member functions or
     global functions.  A declaration of an operator new in a named
     namespace scope is ill-formed."
 
  3.7.3.2[basic.src.dynamic.deallocation] paragraph 1 should be changed
  to say:
    "Like allocation functions, dellocation functions shall be static
     class member functions or global functions.  A declaration of an
     operator delete in a named namespace scope is ill-formed."
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
 *Issue 450:
  ----------
  How is a class operator new/delete looked up?
 
  12.5[class.free] paragraph 2 says:
 
    "When a non-array object or an array of class T is created by a new  -
     expression, the allocation function is looked up in the scope of
     class T using the usual rules."
 
  Does that mean that operator new is looked up as a member of T and
  then in the current scope, or that it is looked up as a member of T
  and then in the enclosing scope of T?  In other words, is this code
  well-formed?
 
  Example 1:
    struct A { };
    struct B {
       char buf[sizeof (A)];
       void *operator new (size_t, void *p, int) { return p; }
       B() { new (buf, 1) A; }
    };
 
  How about this?
 
  Example 2:
    namespace foo {
       void *operator new (size_t, void *p, int) { return p; }
       struct A { };
    }
 
    namespace bar {
       struct B {
          char buf[sizeof (foo::A)];
          B() { new (buf, 1) foo::A; }
       };
    }
 
  Proposal 450:
  -------------
 
  Given the proposed resolution for issue 463 above, which says that
  operator new/delete cannot be declared in a named namespace scope,
  Example 2 is ill-formed.
 
  As for example 1, 12.5[class.free] should be clarified to say that:
  Paragraph 2:
    "When a non-array object or an array of class T is created by a new  -
     expression, the allocation function is looked up in the scope of
     class T; if T does not define the appropriate allocation function
     as a member function, the global allocation function is used."
 
  Paragraph 7:
    "When an object is deleted by a delete-expression, the deallocation
     function is looked up in the scope of the class of the executed
     destructor.  If the class of the executed destructor does not
     define the appropriate deallocation function as a member function,
     the global deallocation function is used."
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
 *Issue 469:
  ---------
  Because new in a new expression and delete in a delete expression are
  keywords and not function names, the syntax ::new and ::delete has
  special meaning.  5.3.4 [expr.new] does not describe this meaning.
 
  Proposal 469:
  -------------
  Add to 5.3.4 [expr.new] paragraph 10:
  "When the keyword new in a new expression is preceeded by the unary
   '::' operator, the global operator new is used to allocate the
   storage."
 
  Add to 5.3.5 [expr.delete] paragraph 8:
  "When the keyword delete in a delete expression is preceeded by the
   unary '::' operator, the global operator delete is used to
   deallocate the storage."
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
 *Issue 435:
  ----------
  Can reference types be newed?
  5.3.4 [expr.new] paragraph 2 says:
    "The new-type in a new-expression is the longest possible sequence
     of new-declarators.  This prevents ambiguities between declarator
     operators &, *, [], and their expression counterparts."
 
  operator &?
 
  Proposal 435:
  -------------
  Reference types cannot be newed.
  The second sentence of 5.3.4 p2 should be changed as follows:
    "This prevents ambiguities between declarator operators *, [], and
     their expression counterparts."
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
 *Issue 453:
  ----------
  Is it permitted for an implementation to create temporaries using
  operator new?  If so, does that require that operator new() be
  accessible in the context in which such a temporary is created?
  Is an implementation allowed to call a replaced operator new whenever
  it likes (storage for RTTI, exception handling, static variables in
  a library)?
 
  Discussion 453:
  ---------------
  On the core reflector, folks have expressed opinions for this either
  ways:
  o Yes
    [Jerry Schwarz in core-5069]:
     This isn't an easy question to answer.  On the one hand, if you
     can't predict how many times your operator new will be called (and
     with what requests) it may make it harder to write a replacement
     operator new.  On the other hand, if you really want to get a
     handle on all allocation of memory and are prepared for an
     unlimited amount of requests then maybe you do want these going
     through your operator new.
 
     On balance I think its best to allow a system to call operator
     new, and treat the amount of space it requests as a kind of
     "Environmental Limit".  If you say a system can only call a
     replacement operator new for an explicit new expression you place a
     severe constraint on the implementation of standard libraries.
 
    [Nathan Myers in core-5071]:
     If it doesn't say you can't use operator new, it means you can.
     Moreover, if you do, you should use the replaceable operator new
     and not some hacked up backdoor allocator; same in the standard
     library.
 
  o No
    [Greg Colvin in core-5073]:
     I have recently struggled with a compiler that calls operator new
     to get space for function scope statics, including the static that
     I was using to control initialization of my replacement operator
     new.  I was quite suprised, since the C standard promises that all
     memory for all statics is acquired at or before program startup (I
     forget the exact wording).  Note that if automatic and static
     storage can be aquired by calling operator new then a bad_alloc
     exception can be thrown most anywhere.  In particular, we will need
     to specify that all standard library calls may throw bad_alloc.
 
    [Andrew Koenig in core-5072]:
     It clearly cannot be permitted in general, because otherwise it
     would be impossible to write a user-defined operator new without
     running the risk of infinite recursion.
 
     So I think the right answer has to be something like this:
     The implementation of the language is never allowed to call a
     user-defined operator new except in response to a new-expression.
     The library, on the other hand, has more latitude.
 
  Proposal 453:
  -------------
  I completely agree with Andy here:
 
  The storage for variables with static storage duration, for data
  structures used for RTTI and exception handling must be acquired at or
  before program startup.
 
  global operator new/delete (either the user-defined ones or the
  implementation-supplied ones) will only be called from new/delete
  expressions and by the functions in the library.
 
  This seems to follow from what the C standard says:
  See 3.1.2.4 (storage durations of objects):
 
  o For objects of static storage duration:
      "For such an object, the storage is reserved ...  prior to program
       start up.
    The C++ standard should probably say something like this in section
    3.7.1 [basic.stc.stc].
 
 o For objects of automatic storage duration:
     "Storage is guaranteed to be reserved for a new instance of such an
      object on each normal entry into a block with which it is
      associated, or on a jump from outside the block to a labeled
      statement in the block or in an enclosed block.  Storage for the
      object is no longer guaranteed to be reserved when execution of
      the block ends in any way.  (Entering an enclosed block suspends
      but does not end execution of the exclosing block.  Calling a
      function suspends but does not end execution of the block
      containing the call."
   The C++ standard should probably say something like this in section
   3.7.2 [basic.stc.auto].
 
  12.2 [class.temporary] should probably indicate that the storage
  for temporaries is not allocated by operator new.
 
  5.2.6[expr.dynamic.cast], 5.2.7[expr.typeid] and 15[except] should
  probably indicate that the storage for the data structures required
  for RTTI and exception handling is not allocated by operator new.
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
 *Issue 470:
  ----------
  Can pointers allocated with a user-declared new with placement be
  deleted?
 
  5.3.5[expr.delete] p2 says:
    "...  in the first alternative (delete object), the value of the
     operand of delete shall be a pointer to a non-array object created
     by a new-expression without a new-placement specification, ... In
     the second alternative (delete array), the value of the operand
     of delete shall be a pointer to a array created by a new-expression
     without a new-placement specification. If not the behavior is
     undefined."
 
  In some situations, it is well-defined what happens even when new
  with placement was called.  Do we want to prohibit these cases?
 
  Proposal 470:
  -------------
  Calling delete for a pointer to an object created by new-expression
  with placement should only be undefined if the library global operator
  new with placement was called.
 
  A user-declared operator new with placement can allocate storage in
  which case using delete on a pointer returned by such a placement new
  should be well-defined.
 
  Replace 5.3.5[expr.delete] p2 to say:
    "...  in the first alternative (delete object), the value of the
     operand of delete shall be a pointer to a non-array object created
     by a new-expression, ...  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.  If not, the behavior is undefined.
     In either alternative, if the operand of the delete expression
     is a pointer to an object created by a new expression with a
     new-placement specification, and if the library operator new with
     placement was used to allocate the storage, the behavior of the
     delete expression is undefined."
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
 *Issue 426:
  ----------
  When can the static type of the operand of the delete expression
  differ from its dynamic type?
 
  5.3.5[expr.delete] paragraph 3 says:
    "In the first alternative (delete object), if the static type of
     the operand is different from its dynamic type, the static type
     shall have a virtual destructor or the behavior is undefined."
 
  This needs to be more specific and say that there must exist an
  inheritance relationship between the static type and the dynamic type,
  eventhough the static type has a virtual destructor.
 
  Proposal 426:
  -------------
  Change 5.3.5[expr.delete] paragraph 3 to say:
    "...  if the static type of the operand is different from its
     dynamic type, the static type shall be a base class of the
     operand's dynamic type and the static type shall have a virtual
     destructor..."
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
 *Issue 471:
  ----------
  When can an implementation change the value of the expression denoting
  the object in a delete expression?
 
  5.3.5[expr.delete] paragraph 4 says:
    "It is unspecified whether the deletion of an object changes its
     value.  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."
 
  Is the code below well-defined?
 
     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;
     }
 
  Can an implementation change the value of bp?
 
  Proposal 471:
  -------------
  The first sentence above:
    "It is unspecified whether the deletion of an object changes its
     value."
  repeats what is already specified in 3.8[basic.life] and should be
  deleted from 5.3.5.
 
  The second sentence:
    "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."
  I believe this intends to say that accessing a pointer after the
  storage it refers to has been deleted result in undefined behavior.
  This rule is already covered in the section on deallocation functions:
  3.7.3.2[basic.stc.dynamic.deallocation] paragraphs 4 and 5:
    "A deallocation function can free the storage referenced by the
     pointer given as its argument and renders the pointer invalid. ...
     An invalid pointer contains an unusable value: it cannot even be
     used in an expression.
 
     If the argument is non-zero, the value of a pointer that refers
     to deallocated space is indeterminate. The effect of dereferencing
     an indeterminate pointer value is undefined."
 
  These changes imply that the example above is well-defined.
  Since no memory is deallocated in the example, 'bp' never points to
  deallocated memory and there is no reason why referring to 'bp'
  should result in undefined behavior.
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
 
o Memory Model
==============
 
  *Issue 515:
  ----------
  3.9[basic.types] paragraph 2 says:
    "For any object type T, the underlying bytes of the object can be
     copied into an array of char or unsigned char.  The copy operation
     is well-defined even if the object does not hold a valid value of
     type T."
 
  o the character array has to be properly aligned for an object of
    type T for this copying to have well-defined behavior.
 
  Proposal 515:
  -------------
 
  Change 3.9[basic.types] paragraph 2 to say:
    "For any object type T, the underlying bytes of the object can be
     copied into an array of char or unsigned char _properly aligned to
     hold an object of type T_.  The copy operation is well-defined even
     if the object does not hold a valid value of type T."
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
 *Issue 511:
  ----------
  3.9[basic.types] paragraph 6 says:
    "...  an object is allocated at an address that is divisible by the
     alignment of its object type."
 
  Alignment restrictions are implementation-defined.  The restriction
  may be another then divisibility of the pointer.  Beside, divisibility
  is not defined for pointers.  The restriction should instead express
  'the requirements of pointers that must point to objects of such
  type'.
 
  Proposal 511:
  -------------
  Replace the sentence above with:
    "...  an object is allocated at an address that respects the
     alignment requirements for its type."
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
 *Issue 464:
  ----------
  Can memcpy be used to copy pointer to members?
 
  3.9[basic.types] paragraph 3 says:
    "For any scalar type T, if two pointers to T point to distinct T
     objects obj1 and obj2, if the value of obj1 is copied into obj2,
     using the memcpy library function, obj2 shall subsequently hold the
     same value as obj1."
 
  Shouldn't this also be valid if T is a pointer to member type?
 
    class A { void f(int); };
    typedef (A::*PM)(int);
 
    {
            A a;
            PM p = A::f;
            PM q;
            memcpy(&q, &p, sizeof(PM));
            (a.*q)(0);
    }
 
  Proposal 464:
  -------------
  Change the sentence above to say:
    "For any scalar type or pointer to member type T, ..."
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
 *Issue 522:
  ----------
  numeric_limits does not contain limits for all fundamental types.
 
  3.9.1[basic.fundamental] paragraph 1 says:
    "Specializations of the standard template numeric_limits (18.2)
     shall specify the largest and smallest values of each for an
     implementation."
 
  This is not true for void, bool, enumeration types which are
  fundamental types.
 
  Proposal 522:
  -------------
  Replace the sentence above with:
    "Specializations of the standard template numeric_limits (18.2)
     shall specify the largest and smallest values of each fundamental
     types (except for void, bool and enumeration types) for an
     implementation."
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
 *Issue Box 20:
  -------------
  3.9.1[basic.fundamental] paragraph 9 says:
    "The representations of integral types shall define values by use of
     a pure binary numeration system."
  Does this means two's complement?
 
  Proposal Box 20:
  ----------------
  The paper presenting the memory model (94-00181=N0568) had a footnote
  to answer this question:
    "A positional representation for integers that uses the binary
     digits 0 and 1, in which the values represented by successive bits
     are additive, begin with 1, and are multiplied by successive
     integral power of 2, except perhaps the bit with the highest
     position.
 
     For any integral type, 2's complement and signed magnitude
     implementations are permitted, and for integral types other than
     character types, 1's complement implementations are also permitted."
 
  This footnote (or maybe a summerized version of it) should be included
  in the WP text.
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
 *Issue Box 20:
  -------------
  3.9.1[basic.fundamental] paragraph 10 says:
    "The type double provides at least as much precision as float,
     and the type long double provides at least as much precision
     as double".
 
  The C standard says:
    "The set of values of the type float is a subset of the set of
     values of the type double; the set of values of the type double is
     a subset of the set of values of the type long double."
 
  Proposal Box 20:
  ----------------
  The sentence quoted from the C standard should be added to 3.9.1
  paragraph 10.