57

As far as the respective language standards go, C offers dynamic memory allocation only through the malloc() family, while in C++ the most common form of allocation is performed by ::operator new(). The C-style malloc is also available in C++, and many "baby's first allocator" examples use it as its core allocation function, but I am curious how contemporary compilers implement the actual production operator-new.

Is it just a thin wrapper around malloc(), or is it implemented fundamentally differently on account of the rather different memory allocation behaviour of a typical C++ program compared to a typical C program?

[Edit: I believe the main difference is usually described as follows: A C program has fewer, larger, long-lived allocations, while a C++ program has many, small, short-lived allocations. Feel free to chime in if that's mistaken, but it sounds like one would benefit from taking this into account.]

For a compiler like GCC it would be easy to just have one single core allocation implementation and use that for all relevant languages, so I wonder if there are differences in the details that try to optimize the resulting allocation performance in each language.


Update: Thanks for all the great answers! It looks like in GCC this is completely solved by ptmalloc, and that MSVC also uses malloc at the core. Does anyone know how the MSVC-malloc is implemented?

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084

5 Answers5

48

Here is the implementation used by g++ 4.6.1:

_GLIBCXX_WEAK_DEFINITION void *
operator new (std::size_t sz) throw (std::bad_alloc)
{
  void *p;

  /* malloc (0) is unpredictable; avoid it.  */
  if (sz == 0)
    sz = 1;
  p = (void *) malloc (sz);
  while (p == 0)
    {
      new_handler handler = __new_handler;
      if (! handler)
#ifdef __EXCEPTIONS
        throw bad_alloc();
#else
        std::abort();
#endif
      handler ();
      p = (void *) malloc (sz);
    }

  return p;
}

This is found in libstdc++-v3/libsupc++/new_op.cc inside the g++ source distro.

As you can see, it's a fairly thin wrapper around malloc.

edit On many systems it is possible to fine-tune the behaviour of malloc, typically by calling mallopt or setting environment variables. Here is one article discussing some features available on Linux.

According to Wikipedia, glibc versions 2.3+ use a modified version of the allocator called ptmalloc, which itself is a derivative of dlmalloc designed by Doug Lea. Interestingly, in an article about dlmalloc Doug Lea gives the following perspective (emphasis mine):

I wrote the first version of the allocator after writing some C++ programs that almost exclusively relied on allocating dynamic memory. I found that they ran much more slowly and/or with much more total memory consumption than I expected them to. This was due to characteristics of the memory allocators on the systems I was running on (mainly the then-current versions of SunOs and BSD ). To counter this, at first I wrote a number of special-purpose allocators in C++, normally by overloading operator new for various classes. Some of these are described in a paper on C++ allocation techniques that was adapted into the 1989 C++ Report article Some storage allocation techniques for container classes.

However, I soon realized that building a special allocator for each new class that tended to be dynamically allocated and heavily used was not a good strategy when building kinds of general-purpose programming support classes I was writing at the time. (From 1986 to 1991, I was the the primary author of libg++ , the GNU C++ library.) A broader solution was needed -- to write an allocator that was good enough under normal C++ and C loads so that programmers would not be tempted to write special-purpose allocators except under very special conditions.

This article presents a description of some of the main design goals, algorithms, and implementation considerations for this allocator.

NPE
  • 486,780
  • 108
  • 951
  • 1,012
  • Thank you very much! How interesting (and also slightly disappointing). Maybe `malloc()` is just so good already that it handles most situations with satisfactory performance. – Kerrek SB Sep 16 '11 at 11:32
  • 1
    @Kerrek SB: To be frank, my intuition doesn't suggest that C and C++ have vastly different allocation patterns. That said, my intuition could of course be wrong. :-) – NPE Sep 16 '11 at 11:44
  • @Kerrek: performnce?? If you just strip-off of the "fuzziness", in case malloc didn't fail, the above code becomes just ... p = malloc(...); if(!p) {} return p; Consider the time the OS will spend into the execution of malloc, I dont see how if(!p) (in assembler is just a JZ intruction) can change performace! – Emilio Garavaglia Sep 16 '11 at 11:58
  • 3
    @Emilio: I was referring to the internal allocation algorithms that `malloc()` uses. You see, it could be that `malloc()` is tuned to perform well for C-type allocations, but would actually cause lots of fragmentation or page faults or whatnot when used by C++ containers (just as an example). So my question is if or how one would be able to use knowledge about a language's typical needs to improve the design of the core allocator. (And apparently GCC doesn't.) – Kerrek SB Sep 16 '11 at 12:01
  • 1
    @kerrek: Sorry for the misudertanding. In fact malloc itself is implemented by a call to an OS API (typically HeapAlloc, on windows) that in turn wants to operate on a heap whose allocation policy is specifyed when the heap itself is created via CreateHeap. You have to go two layers down, to get that point. I hope the CRT library uses the proper policy when invoked by C or C++. – Emilio Garavaglia Sep 16 '11 at 12:07
  • @Emilio: Interesting; I'd love to hear more about the Windows interna. I assume that's MSVC-specific? – Kerrek SB Sep 16 '11 at 12:22
  • @kerrek: "I assume that's MSVC-specific?" Yes and no ... HeapAlloc and CreateHeap are APIs every compiled program must call to prepare the environment for its execution. Every compiler must do this. The way they do (How is the heap initially wide, what grow and shrink policy it has, what "fit"ting algorithm etc.) depends on the compiler ... or better, to the runtime library that comes with the compiler. basically the heap management parameter are a choiche of the compiler manufacturer. Unless you write your own operator new operating in your own created heap, with your own defined policies! – Emilio Garavaglia Sep 16 '11 at 12:34
  • @Kerrek SB: Please see my edit. It's the result of some brief archaeological research I've just carried out. :-) – NPE Sep 16 '11 at 12:56
  • @aix: Great, thank you! Sounds like `ptmalloc` really satisfies most needs. Good to know that its design was specifically informed by its use in C++. I'll probably accept this answer, but I'll leave this to percolate for a bit -- I'm still a little curious about non-GCC compilers. – Kerrek SB Sep 16 '11 at 13:38
  • Note: several studies have shown that `tcmalloc` (from Google) or `jemalloc` (now in Facebook) can run circles around a typical malloc implementation. Also, at least `tcmalloc` includes debugging/monitoring hooks (I think je does too). – Matthieu M. Sep 16 '11 at 17:56
  • @kernel SB - From the very top level, malloc() internally calls the brk() system call to get going. – Anoop Menon Sep 17 '11 at 11:51
  • a bit off-topic . /* malloc (0) is unpredictable; avoid it. */ if (sz == 0) sz = 1; why would anyone malloc(0)? – Alex Sep 20 '11 at 19:16
  • @Alex: don't think of someone writing malloc (0), think about writing malloc (some_variable_number) where some_variable_number could be zero. – Francesco Oct 30 '11 at 19:40
15

In most implementations operator new() just calls malloc(). In fact even The Standard suggests that as a default stratege. Of course you can implement your own operator new, usually for a class if you want better performance, but the default is usually just calling malloc().

Community
  • 1
  • 1
sharptooth
  • 167,383
  • 100
  • 513
  • 979
  • Actually, where would I find the source code for that in GCC? This isn't part of the headers. Is it in the libstdc++ sources somewhere? – Kerrek SB Sep 16 '11 at 11:17
  • @Kerrek SB: An indirect proof is that `free( new char )` seems to run okay. – sharptooth Sep 16 '11 at 11:38
  • very naughty :-) It's curious though that two rather different languages could be satisfied by the exact same allocation algorithms. Maybe that just speaks to the quality of the design of `malloc()`... – Kerrek SB Sep 16 '11 at 11:39
  • 1
    @Kerrek SB:In fact `malloc()` "just works" - yes, it can be very slow for some specific scenarios, but it "just works", so unless the user knows what exactly he wants changed he has to use some default and that is `malloc()`. – sharptooth Sep 16 '11 at 11:45
13

glibc new operator is a thin wrapper around malloc. And glibc malloc uses different strategies for different size allocation requests. You can see the implementation, or at least the comments here.

Here's an excerpt from the comments in malloc.c:

/*
47   This is not the fastest, most space-conserving, most portable, or
48   most tunable malloc ever written. However it is among the fastest
49   while also being among the most space-conserving, portable and tunable.
50   Consistent balance across these factors results in a good general-purpose
51   allocator for malloc-intensive programs.
52 
53   The main properties of the algorithms are:
54   * For large (>= 512 bytes) requests, it is a pure best-fit allocator,
55     with ties normally decided via FIFO (i.e. least recently used).
56   * For small (<= 64 bytes by default) requests, it is a caching
57     allocator, that maintains pools of quickly recycled chunks.
58   * In between, and for combinations of large and small requests, it does
59     the best it can trying to meet both goals at once.
60   * For very large requests (>= 128KB by default), it relies on system
61     memory mapping facilities, if supported.
*/
cyco130
  • 4,654
  • 25
  • 34
10

On Visual C++, stepping into a new expression leads me to this snippet in new.cpp:

#include <cstdlib>
#include <new>

_C_LIB_DECL
int __cdecl _callnewh(size_t size) _THROW1(_STD bad_alloc);
_END_C_LIB_DECL

void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
        {       // try to allocate size bytes
        void *p;
        while ((p = malloc(size)) == 0)
                if (_callnewh(size) == 0)
                {       // report no memory
                static const std::bad_alloc nomem;
                _RAISE(nomem);
                }

        return (p);
        }

So VC++'s new also wraps the malloc() call.

In silico
  • 51,091
  • 10
  • 150
  • 143
  • 1
    The malloc implementation of VC is actually thin wrapper around RtlAllocateHeap in ntdll.dll. – newgre Jun 14 '13 at 21:45
2

It's not a matter of performance: pA = new A has a different side effect than pA = (A*)malloc(sizeof(A));

In the second one, the A's constructor is not called. To come to a same effect you should do

pA = (A*)malloc(sizeof(A));
new(pA)A();

where new is the "placement new"...

void* operator new(size_t sz, void* place) 
{ return place; }
Emilio Garavaglia
  • 20,229
  • 2
  • 46
  • 63
  • 7
    I'm not asking about the `new` expression. I'm well aware of the core C++ language concepts. I'm asking specifically about `::operator new()` (the default, not the placement one), which is the allocation function used by the `new` expression. – Kerrek SB Sep 16 '11 at 12:05