I Learned Something New About New

In my last post, I had mentioned that I found a phenomenon that made no sense to me. It had to do with initializing the members of a structure when calling new. Since I can’t let sleeping dogs lie, I went on an adventure to find my answers and I’d like to share what I found.

I simplified the code from my previous example to the bare minimum required to demonstrate the behavior I was seeing, and it looks like:

typedef struct Foo {
  void *bar;
} Foo;

int main( void ) {
  Foo *f = new Foo();
  return 0;
}

If you look at the optimized assembly generated for this code, it will look about like this (regardless of the compiler used):

  push	4
  call	??2@YAPAXI@Z ; operator new
  add	esp, 4
  test	eax, eax
  je	SHORT $LN5@main
  mov	DWORD PTR [eax], 0
$LN5@main:
  xor	eax, eax
  ret	0

What confused me about this assembly was the fact that it was testing for nullptr to be returned from operator new, and if it wasn’t, it was zero initializing the member of the structure. The fact that it is testing for nullptr makes no sense because operator new doesn’t return nullptr, it throws a std::bad_alloc exception by default. What’s more, initializing the structure’s data member to zero goes against everything we’ve been taught about C++! The mantra we usually hear is that the only zero initialization that happens involves statics and globals. So why would optimized assembly be doing these things?

Initially, I tried to find my answers by changing the code. First, I thought that perhaps it had something to do with Foo being a struct, so I changed the declaration to class and saw no change. Then I thought it had something to do with there being only a single member, so I added another one and the only change I saw was that both members were being zero initialized. Then I decided that playing Monkey At The Keyboard was not the way to learn!

When I was writing the last blog posting, I tested four major compilers (MSVC, gcc, clang and ICC) and they all exhibited the same behavior. So I knew this was not a compiler quirk, but something mandated by the standard, which is where I started hunting for information. Usually, I can find everything I need in the standard pretty quickly. I’ve read it enough times that I’m familiar with the structure and tone of the document. But this time, I could find nothing specific to my situation. So I did the next best thing — I downloaded a compiler and looked at the source code.

You can get the source code for clang and LLVM freely online, and by virtue of having contributed in the past I quickly found what I was looking for in the code generator for C++ expressions. There is code for emitting instructions related to new expression initializers, and the comments pointed me to the relevant bits and pieces in the standard. It turns out the reason I couldn’t find my answer initially was because it involved interactions from two sections of the specification.

The [expr.new]p15 section discusses how a new expression creating an object performs its initialization, and [dcl.init]p5-6 discuss how zero and default initialization work. It turns out that the way in which you call new affects the way initialization works!

Foo *f = new Foo();

is different from

Foo *f = new Foo;

According to expr.new, if the new-initializer (the stuff between the parenthesis) is omitted, the object is default initialized. If the new-initializer is not omitted, the object follows the standard direct initialization rules. The dcl.init section describes what these both mean, but essentially, default initialization calls the default constructor (which is not present in my example), or leaves the memory uninitialized. The direct initialization rules when the new-initializer is present are covered under the definition for zero initialization. Specifically, for class types, each non-static data member, padding and base class subobject is zero initialized.

This means that calling new Foo() causes zero initialization, and new Foo causes no initialization to happen. Changing the code to:

typedef struct Foo {
  void *bar;
} Foo;

int main( void ) {
  Foo *f = new Foo;
  return 0;
}

changes the corresponding optimized assembly to:

  push	4
  call	??2@YAPAXI@Z  ; operator new
  add	esp, 4
  xor	eax, eax
  ret	0

This was certainly a revelation to me! However, it doesn’t change my views about how to write code because the addition of a constructor behaves via default initialization instead of zero initialization. For instance:

class Foo {
  void *bar;
public:
  Foo() {}
};

int main( void ) {
  Foo *f = new Foo();
  return 0;
}

This is the same as my first example, except that Foo now has a do-nothing constructor. This produces the following optimized assembly:

??0Foo@@QAE@XZ PROC
  mov	eax, ecx
  ret	0


_main	PROC
  push	4
  call	??2@YAPAXI@Z  ; operator new
  add	esp, 4
  xor	eax, eax
  ret	0

You’ll notice that not only is the zero-initialization not present, and the default constructor isn’t even called (since it’s not used). So as a caller it is not safe to rely on including parenthesis to ensure the members are zero-initialized. This simply turned out to be one of those interesting little language quirks that probably don’t make a whit of difference in the real world. But hopefully you found it to be educational!

This entry was posted in C/C++ and tagged , , , . Bookmark the permalink.

One Response to I Learned Something New About New

  1. Dan says:

    This is one of those dusty corners of C++ that I’m aware of, but haven’t internalized… or put another way, I knew there was *some* difference, but I didn’t remember when it cropped up exactly, or what the differences in behavior were.

    I applaud your diligence in hunting this down – and also for explaining it.

Leave a Reply

Your email address will not be published. Required fields are marked *