Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++11 VS2013 class POD member initialization

I've looked everywhere for an explanation for this but am coming up short. I'm seeing this behavior from the VS2013 v120 platform toolset, but when I set the toolset to v90 (the VS2008 toolset) everything is uninitialized. I believe this is due to some change in C++11, but it could also be an anomaly of the v120 platform toolset.

Can anyone explain what's going on here on the C++/C++11 level? That is, why is b zeroed out? And why is j not also zeroed out? (i.e. why is the behavior different for structs than for classes)

Also, I know the way I'm outputting data is undefined behavior, please ignore that. It's easier to post that way here than a debugger window. This is running on 32-bit, so pointers are the same size as an unsigned int.

Consider the following code:

#include <iostream>

class Foo {
public:
  int a,
      *p;
};

class Bar {
public:
  Bar(){}
  int a,
      *p;
};

struct Jar {
  Jar(){}
  int a,
      *p;
};

int main() {
  Foo f;
  Bar b;
  Jar j;
  std::cout << std::hex; // please excuse this undefined-behavior producing test code, it's more simple to show this than a debugger window on SO (this is on 32-bit)
  std::cout << "f: " << ((unsigned*)&f)[0] << ' ' << ((unsigned*)&f)[1] << std::endl;
  std::cout << "b: " << ((unsigned*)&b)[0] << ' ' << ((unsigned*)&b)[1] << std::endl;
  std::cout << "j: " << ((unsigned*)&j)[0] << ' ' << ((unsigned*)&j)[1] << std::endl;
  return 0;
}

This is the output:

f: cccccccc cccccccc  
b: 0 0  
j: cccccccc cccccccc

EDIT:
Here is the disassembly I see associated with Bar b; The __autoclassinit2 is zeroing the memory. It is not part of the constructor but rather is zeroed before the constructor call.

  Bar b;
00886598  push        8  
0088659A  lea         ecx,[b]  
0088659D  call        Bar::__autoclassinit2 (0881181h)  
008865A2  lea         ecx,[b]  
008865A5  call        Bar::Bar (0881109h)  
like image 291
Apriori Avatar asked Jul 29 '14 23:07

Apriori


1 Answers

All your types contain data members that are built-in types, so none of them will be zero-initialized unless you do one of the following (taking the example of Foo):

Initialize the members in the default constructor:

class Foo {
public:
  Foo() : a(), p() {}
  int a,
      *p;
};

or non-static data member initializers (brace-or-equal-initializer)

class Foo {
public:
  int a = 0,
      *p = nullptr;
};

or leave Foo unchanged, and value initialize the instance

Foo f{};

Using the original example, I cannot reproduce the result you observe using VS2013, 32-bit Debug build. The output I get is

f: cccccccc cccccccc
b: cccccccc cccccccc
j: cccccccc cccccccc

EDIT : I am able to reproduce the behavior where b is being zero-initialized. This happens if you enable the /sdl (Security Development Lifecycle checks) compiler switch (under Configuration Properties -> C/C++ -> General).

From the MSDN documentation for the switch:

When /sdl is enabled, the compiler generates code to perform these checks at run time:
...
— Performs class member initialization. Automatically initializes all class members to zero on object instantiation (before the constructor runs). This helps prevent the use of uninitialized data associated with class members that the constructor does not explicitly initialize.

This blog post even mentions the __autoclassinit function, although the heuristics he lists don't exactly match what we're observing because the behavior of this feature has changed between VS2012 and VS2013.

Also worth nothing is that the compiler seems to not only distinguish between aggregates (Foo) and non-aggregates (the other two), which makes some sense, but, for some truly bizarre reason, it will only perform this zero-initialization if you use the class-key class, and not struct, in the class definition.

like image 136
Praetorian Avatar answered Sep 30 '22 01:09

Praetorian