Topic: Memory Allocation (was auto_ptr effici


Author: "Bill Wade" <bill.wade@stoner.com>
Date: 1996/11/26
Raw View
Steve Clamage <clamage@taumet.eng.sun.com> wrote in article
<199611222111.NAA13403@taumet.eng.sun.com>...
> In particular, the C++ implementation is allowed to assume that every
> allocation function satisfies the rather minimal requirements of the
> standard. (Has a specified calling sequence; allocates proper storage,
> or fails by returning null or by throwing an exception.)

It seems these requirements are enough to ensure that any portable operator
new must be implemented in terms of the compiler-supplied ::operator new or
::operator new[].  For instance the universally used Hitchhiker IIc has a
conforming compiler with the following alignment restrictions:
  char: 1
  short: 2
  int: 4
  double: 8
  class with vtable (virtual functions): 42
So new of any object of 42 bytes or more must be on a boundary of 168,
which is the least common multiple of the numbers above.  Naive compiler
users might implement their operator new in terms of the 'C' malloc(), as
suggested by a footnote in the A95 draft.  However since 'C' doesn't have
to deal with vtable pointers, malloc() might be content with 8-byte
alignment which is suitable for C, but not C++.

I can imagine some ways around this problem
  1) The standard could say that C++ may not have tighter alignment
restrictions than C.
This would mean the Hitchhiker IIc is not conforming, and not an issue.
  2) The standard could say that the malloc() visible to C++ must meet C++
alignment restrictions, not just C alignment restrictions.
  3) A series of defines or compiler constants, similar to <limits.h>,
could specify alignment restrictions.  This, along with well defined
integer/pointer conversions might be workable (but extremely ugly).
  4) This could just be a quality of implementation issue (i.e. no portable
answer).
I think the current situation is (4).

The standard seems to want to make all 'new' functions usable for all 'new'
operations.  In other words it is reasonable for a programmer to write:
  class foo
  {
  public:
    char x[27];
    void* operator new(size_t);
    void operator delete(void*);
  };
  class bar
  {
  public:
    bar():d(2){}
    double d;
  };
  bar* p = (bar*) foo::new(27);
  assert(sizeof(bar) < sizeof(foo));
  p = new(p) bar;

Of course if the stuff above is reasonable, the following implementation of
foo::operator new() is not reasonable:

  static foo fast_foo_helper[2];
  void* foo::operator new(size_t size)
  {
    static int so_far;

    // Try to get an enormous speed advantage compared to ::new()
    if(size != sizeof(foo) || so_far > 1) return ::new(size);
    return fast_foo_helper + so_far++;
  }

  void foo::operator delete(void* p)
  {
    if(p != fast_foo_helper && p != fast_foo_helper+1)
 ::delete p;
  }

In my opinion the second block of code is more reasonable (perhaps some
more profiling would be a good idea first) than the first block of code
which uses foo::new to allocate space for a bar object.

Unfortunately, if I want to sell a foo class, with a fast allocator, my
code is buggy (according to the standard) if my allocator won't work for
allocating other kinds of objects.  My code isn't portable unless every
block I return was originally allocated with operator new or is the first
element of an operator new[] result.
---
[ comp.std.c++ is moderated.  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                             ]





Author: fjh@mundook.cs.mu.OZ.AU (Fergus Henderson)
Date: 1996/11/26
Raw View
"Bill Wade" <bill.wade@stoner.com> writes:

>Steve Clamage <clamage@taumet.eng.sun.com> wrote:
>> In particular, the C++ implementation is allowed to assume that every
>> allocation function satisfies the rather minimal requirements of the
>> standard. (Has a specified calling sequence; allocates proper storage,
>> or fails by returning null or by throwing an exception.)
>
>It seems these requirements are enough to ensure that any portable operator
>new must be implemented in terms of the compiler-supplied ::operator new or
>::operator new[].

No, you can write a strictly conforming operator new using malloc().
It's also possible to write a portable (though not strictly conforming,
alas!) operator new using storage from char arrays.

>For instance the universally used Hitchhiker IIc has a
>conforming compiler with the following alignment restrictions:
>  char: 1
>  short: 2
>  int: 4
>  double: 8
>  class with vtable (virtual functions): 42
>So new of any object of 42 bytes or more must be on a boundary of 168,
>which is the least common multiple of the numbers above.  Naive compiler
>users might implement their operator new in terms of the 'C' malloc(), as
>suggested by a footnote in the A95 draft.  However since 'C' doesn't have
>to deal with vtable pointers, malloc() might be content with 8-byte
>alignment which is suitable for C, but not C++.

That's not correct.  The malloc() function in a conforming implementation
must return memory that is suitably aligned for any object, including
C++ objects.  For example, the following is a strictly conforming C++
program:

 #include <new>
 using std;

 struct S {
  virtual void foo() {};
 };

 int main() {
  // allocate space for an object using malloc()
  // and then construct an object using placement new
  // in the space allocated by malloc
  S * p = (S *) malloc(sizeof(S));
  new (p) S;

  // use the object we just created
  p->foo();

  // destroy the object and free the memory
  p->~S();
  free(p);

  return 0;
 }

>  2) The standard could say that the malloc() visible to C++ must meet C++
>alignment restrictions, not just C alignment restrictions.

That's what the standard does say.  The standard doesn't have
two notions of "C alignment restrictions" and "C++ alignment restrictions".
It just has a single notion.  The requirement on malloc is that
it return something properly aligned for any object, not just any C object.

>The standard seems to want to make all 'new' functions usable for all 'new'
>operations.

Yes.  This overconstrains things, IMHO.  (It does make it
easier to get an efficient implementation of auto_ptr, but
I don't think that was the original intention, I think the
wording was just unintentially overconstraining.)

--
Fergus Henderson <fjh@cs.mu.oz.au>   |  "I have always known that the pursuit
WWW: <http://www.cs.mu.oz.au/~fjh>   |  of excellence is a lethal habit"
PGP: finger fjh@128.250.37.3         |     -- the last words of T. S. Garp.
---
[ comp.std.c++ is moderated.  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                             ]





Author: clamage@taumet.eng.sun.com (Steve Clamage)
Date: 1996/11/23
Raw View
In article H000069b01347e6a@MHS, paul.black@vf.vodafone.co.uk writes:
>fjh@murlibobo.cs.mu.OZ.AU (Fergus Henderson) wrote:
>>
>> That's not correct, because all allocation functions must satisfy
>> the requirements in 3.7.3:
>>
>> |   3.7.3  Dynamic storage duration                    [basic.stc.dynamic]
>> |
>> | 3 Any allocation and/or deallocation functions defined in a C++  program
>> |   shall conform to the semantics specified in this subclause.
>> |
>> |   3.7.3.1  Allocation functions           [basic.stc.dynamic.allocation]
>> [...]
>> | 2 The function shall return the address of the start of a block of stor-
>> |   age whose length in bytes shall be at least as large as the  requested
>> |   size.  [...]  The pointer returned shall  be  suit-
>> |   ably  aligned  so that it can be assigned to a pointer of any type and
>> |   then used to access the object  or  array  in  the  storage  allocated
>
>Yikes! Does this mean when I write code, as well as being portable,
>maintainable, blah blah blah, it must now comply with the ANSI C++ standard?

Only if you care whether it works. That isn't a change from existing
practice in C or C++.

In particular, the C++ implementation is allowed to assume that every
allocation function satisfies the rather minimal requirements of the
standard. (Has a specified calling sequence; allocates proper storage,
or fails by returning null or by throwing an exception.)

For example, there are certain requirements on the formal parameters and
return type of allocation functions. If you write an allocator which
does not meet those requirements, you cannot depend on your code
compiling or working.

Similarly, if you write an allocator that returns storage which is
too small or improperly aligned, the compiler and run-time system
are not obligated to ensure that your program works properly. How
could they?

If your allocator follows the guidelines, you may assume that the
implementation will behave properly.

Let's look at it another way. The implementation supplies memory
management routines which satisfy the requirements of the language.
You are allowed to add more functions or replace the existing functions.
If you choose to do so, you need to satisfy the interface requirements
specified in the language definition. Otherwise, how would the
compiler and runtime system know what to do with those functions?

---
Steve Clamage, stephen.clamage@eng.sun.com
---
[ comp.std.c++ is moderated.  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                             ]