R@M3$H.NBlog

malloc vs new

02 October, 2013 - 5 min read

From the C++ FQA Lite:

[16.4] Why should I use new instead of trustworthy old malloc()?

FAQ: new/delete call the constructor/destructor; new is type safe, malloc is not; new can be overridden by a class.

FQA: The virtues of new mentioned by the FAQ are not virtues, because constructors, destructors, and operator overloading are garbage (see what happens when you have no garbage collection?), and the type safety issue is really tiny here (normally you have to cast the void* returned by malloc to the right pointer type to assign it to a typed pointer variable, which may be annoying, but far from "unsafe").

Oh, and using trustworthy old malloc makes it possible to use the equally trustworthy & old realloc. Too bad we don't have a shiny new operator renew or something.

Still, new is not bad enough to justify a deviation from the common style used throughout a language, even when the language is C++. In particular, classes with non-trivial constructors will misbehave in fatal ways if you simply malloc the objects. So why not use new throughout the code? People rarely overload operator new, so it probably won't get in your way too much. And if they do overload new, you can always ask them to stop.

  1. Operator new constructs an object (calls constructor of object), malloc does not.
  2. Operator new is an operator, malloc is a function.
  3. Operator new can be overloaded, malloc cannot be overloaded.
  4. Operator new throws an exception if there is not enough memory, malloc returns a NULL.
  5. Operator new[] requires to specify the number of objects to allocate, malloc requires to specify the total number of bytes to allocate.
  6. malloc() returns void *, which has to be explicitly cast to the desired type but new returns the proper type.
  7. Operator new/new[] must be matched with operator delete/delete[] to deallocate memory, malloc() must be matched with free() to deallocate memory.
  8. The new/delete couple does not have a realloc alternative that is available when malloc/free pair is used. realloc is used to resize the length of an array or a memory block dynamically

Deficiencies fixed by new for C++ code

  1. malloc is not typesafe in any meaningful way. In C++ you are required to cast the return fromvoid*. This potentially introduces a lot of problems:

    #include <stdlib.h>
    
    struct foo {
      double d[5];
    }; 
    
    int main() {
      foo *f1 = malloc(1); // error, no cast
      foo *f2 = static_cast<foo*>(malloc(sizeof(foo)));
      foo *f3 = static_cast<foo*>(malloc(1)); // No error, bad
    }
  2. It's worse than that though. If the type in question is POD (plain old data) then you can semi-sensibly use malloc to allocate memory for it, as f2 does in the first example.
It's not so obvious though if a type is POD. The fact that it's possible for a given type to change from POD to non-POD with no resulting compiler error and potentially very hard to debug problems is a significant factor. For example if someone (possibly another programmer, during maintenance, much later on were to make a change that caused `` foo `` to no longer be POD then no obvious error would appear at compile time as you'd hope, e.g.:



    struct foo {
      double d[5];
      virtual ~foo() { }
    };



would make the `` malloc `` of `` f2 `` also become bad, without any obvious diagnostics. The example here is trivial, but it's possible to accidentally introduce non-PODness much further away (e.g. in a base class, by adding a non-POD member). If you have C++11/boost you can use `` is_pod `` to check that this assumption is correct and produce an error if it's not:



    #include <type_traits>
    #include <stdlib.h>
    
    foo *safe_foo_malloc() {
      static_assert(std::is_pod<foo>::value, "foo must be POD");
      return static_cast<foo*>(malloc(sizeof(foo)));
    }



Although boost is [unable to determine if a type is POD](http://www.boost.org/doc/libs/1_47_0/libs/type_traits/doc/html/boost_typetraits/reference/is_pod.html) without C++11 or some other compiler extensions.
  1. malloc returns NULL if allocation fails. new will throw std::bad_alloc. The behaviour of later using a NULL pointer is undefined. An exception has clean semantics when it is thrown and it is thrown from the source of the error. Wrapping malloc with an appropriate test at every call seems tedious and error prone. (You only have to forget once to undo all that good work). An exception can be allowed to propagate to a level where a caller is able to sensibly process it, where as NULL is much harder to pass back meaningfully. We could extend our safe_foo_mallocfunction to throw an exception or exit the program or call some handler:

    #include <type_traits>
    #include <stdlib.h>
    
    void my_malloc_failed_handler();
    
    foo *safe_foo_malloc() {
      static_assert(std::is_pod<foo>::value, "foo must be POD");
      foo *mem = static_cast<foo*>(malloc(sizeof(foo)));
      if (!mem) {
         my_malloc_failed_handler();
         // or throw ...
      }
      return mem;
    }
  2. Fundamentally malloc is a C feature and new is a C++ feature. As a result malloc does not play nicely with constructors, it only looks at allocating a chunk of bytes. We could extend oursafe_foo_malloc further to use placement new:

    #include <stdlib.h>
    #include <new>
    
    void my_malloc_failed_handler();
    
    foo *safe_foo_malloc() {
      void *mem = malloc(sizeof(foo));
      if (!mem) {
         my_malloc_failed_handler();
         // or throw ...
      }
      return new (mem)foo();
    }
  3. Our safe_foo_malloc function isn't very generic - ideally we'd want something that can handle any type, not just foo. We can achieve this with templates and variadic templates for non-default constructors:

    #include <functional>
    #include <new>
    #include <stdlib.h>
    
    void my_malloc_failed_handler();
    
    template <typename T>
    struct alloc {
      template <typename ...Args>
      static T *safe_malloc(Args&&... args) {
        void *mem = malloc(sizeof(T));
        if (!mem) {
           my_malloc_failed_handler();
           // or throw ...
        }
        return new (mem)T(std::forward(args)...);
      }
    };
Now though in fixing all the issues we identified so far we've practically reinvented the default `` new ``operator. If you're going to use `` malloc `` and placement `` new `` then you might as well just use `` new ``to begin with!

END