0

Consider this code:

#include <iostream>
using namespace std;

struct Z
{
    Z() { cout << "ctor Z" << endl; }
};

struct A
{
    A() = default;
    A(int xx) : x(xx) {}
    int x;
    Z z;
};

int main()
{
    A a;
    cout << "a.x = " << a.x << endl;
}

Compiled with newest GCC it prints:

ctor Z
a.x = 0

But compiled with newest Clang it prints:

ctor Z
a.x = 4198800

Why? Can someone explain this? Does GCC initiates the 'x' prinitive value while it doesn't have to do it? Or, maybe Clang doesn't initiante the 'x' with 0, while it should do it?

The same happens when I remove my constructors from the "A" struct, so it is not a matter of the constructor being marked as 'default' or something like that. To put this simply - one compiler generates code which calls constructors of its members being class objects, and also members being primitives. The other compiler runs constructor of only the class objects members.

Why?

tadman
  • 208,517
  • 23
  • 234
  • 262
YotKay
  • 1,127
  • 1
  • 9
  • 25
  • See: https://stackoverflow.com/a/16782131/1526322, the standard says the value is indeterminate. GCC zeroes it. You should not use the value before defining it. You should always consider the standard to be more correct than your compiler. – Knox Jul 06 '17 at 22:04

3 Answers3

3

You need to initialize x in every constructor, not just one of them. Using the default constructor leaves it uninitialized. GCC might zero it out, or it might be zeroed out when the process is launched. clang is doing it right here by having garbage data; it shows there's a bug.

You could fix this with:

A(int xx = 0) : x(xx) {}

Where the other constructor is no longer required since you've provided a default value.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
tadman
  • 208,517
  • 23
  • 234
  • 262
  • 1
    The 0 is just as "garbage". GCC's behaviour is just as "right". – Lightness Races in Orbit Jul 06 '17 at 22:04
  • 1
    An another alternative: OP could add a default initializer: `int x = 0;`. – HolyBlackCat Jul 06 '17 at 22:06
  • Or say `A a{};` – juanchopanza Jul 06 '17 at 22:06
  • @juanchopanza It seems to work, but how? I thought because `A` has a custom ctor, `{}` [shouldn't work](http://en.cppreference.com/w/cpp/language/aggregate_initialization) as an aggregate initialization. – HolyBlackCat Jul 06 '17 at 22:18
  • 3
    @HolyBlackCat It's not aggregate initialization; however if and only if the default constructor is defaulted then value-initialization also does zero-initialization. A constructor of `A() {}` would not lead to zero-initialization here. (Yet another weird corner case of C++ initialization).. – M.M Jul 06 '17 at 22:26
  • 1
    Just a wild guess here, but maybe the rationale is that a defaulted default constructor does not disqualify the class from being an aggregate; and people expect `A{}` to zero-initialize an aggregate, so if some other change is made to the class that inadvertently disqualifies it from being an aggregate (actually the definition of aggregate seems to change in every standard, so this is not unlikely), we don't want to introduce bugs due to the object suddently not being zero-initialized – M.M Jul 06 '17 at 22:30
2

It shouldn't be a surprise that different compilers produce different code. Otherwise there would only be one compiler.

The differences between them are particularly noticeable in the way they treat instances of undefined behaviour, such as reading the indeterminate "value" of an uninitialised object (e.g. A::x here).

It's both extremely complicated and utterly pointless to rationalise about why specific compilers specifically resulted in specific values here, because there are so very many possible explanations, all of which could change the next time you hit "compile". It's easier to discuss when an implementation fills a memory space with e.g. 0xCCCCCCCC in debug mode, so that you can see when you forgot to initialise something. That hasn't happened in either case here, though.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
2

x is being default initialized. Looking at http://en.cppreference.com/w/cpp/language/default_initialization

Default initialization is performed in three situations:

1) when a variable with automatic, static, or thread-local storage duration is declared with no initializer;

2) when an object with dynamic storage duration is created by a new-expression with no initializer or when an object is created by a new-expression with the initializer consisting of an empty pair of parentheses (until C++03);

3) when a base class or a non-static data member is not mentioned in a constructor initializer list and that constructor is called.

The effects of default initialization are:

  • if T is a non-POD (until C++11) class type, the constructors are considered and subjected to overload resolution against the empty argument list. The constructor selected (which is one of the default constructors) is called to provide the initial value for the new object;

  • if T is an array type, every element of the array is default-initialized;

  • otherwise, nothing is done: the objects with automatic storage duration (and their subobjects) are initialized to indeterminate values.

Amadeus
  • 10,199
  • 3
  • 25
  • 31
  • This solves my problem, thank you. So, as I understand, the constructor of 'Z' is called becaused it is a non-POD, and 'x' is left uninitialized because of the third bullet "otherwise", right? – YotKay Jul 07 '17 at 05:12
  • In the case of Z, item 3 is being applicable – Amadeus Jul 09 '17 at 05:42
  • Item 3 for Z? Item 3 says that nothing is done, whereas the constructor of Z is invoked. – YotKay Jul 09 '17 at 07:43