3

I've been messing around with a default constructor example inspired by this answer. This one works fine:

class Foo {
public:
    int x;
    Foo() = default;
};

int main() {    
    for (int i = 0; i < 100; i++) {
        Foo* b = new Foo();
        std::cout << b->x << std::endl;
    }

But when I try this out with a class instance on the stack it does not! For example, if I instantiate the class like Foo b I get an error saying uninitialised local variable 'b' used.

However, when I change the constructor to Foo() {}, instantiation with Foo b works fine, and I see random garbage from b.x as expected.

Why doesn't a default constructor work when I instantiate a class on the stack?

I compiled the code with MVSC C++17: Screenshot

Kaiyakha
  • 1,463
  • 1
  • 6
  • 19
  • Can't reproduce: https://ideone.com/BDubw3 – lorro Aug 14 '22 at 23:33
  • @lorro Compiler-specific? I use MSVC C++17 – Kaiyakha Aug 14 '22 at 23:36
  • `Foo b{};` as a workaround? – lorro Aug 14 '22 at 23:43
  • @lorro it is not about solving some problem, I wonder what there is so specific about `default` constructor that it does not want to compile on the stack – Kaiyakha Aug 14 '22 at 23:46
  • 1
    It's that MSVC can prove it for the stack-based case, therefore it emits an error. – lorro Aug 14 '22 at 23:48
  • New will zero initialize the int, won’t it? – Taekahn Aug 14 '22 at 23:54
  • 1
    @Taekahn `new` is not responsible for initialisation – Kaiyakha Aug 14 '22 at 23:55
  • 1
    @Kaiyakha -- For MSVC, do you have "treat warnings as errors" compiler option set? If so, then that is not a C++ error -- that is you setting the compiler to do as you stated -- treat warnings as errors. – PaulMcKenzie Aug 15 '22 at 00:37
  • @PaulMcKenzie well, I still managed to launch some of the examples and see random garbage of an uninitialised variable, so what you suggest does not sound reasonable I think – Kaiyakha Aug 15 '22 at 00:41
  • @Kaiyakha No, what I am saying is that the error you have in that image -- that is *not* a C++ error. It is a warning by the compiler. A warning should never stop the application from being built *unless you have stated to treat warnings as errors*. – PaulMcKenzie Aug 15 '22 at 00:42
  • @Kaiyakha -- Also, you should not use observable behavior as a way to figure out the rules of C++. This is the mistake so many new programmers make, and that is to say "my program is doing , so the rules of C++ means ". No -- the rules are determined by what's written in the C++ standard document. – PaulMcKenzie Aug 15 '22 at 00:45
  • @PaulMcKenzie wrong screenshot, _uninitialised local variable_ is C4700 – Kaiyakha Aug 15 '22 at 00:46
  • 1
    @Kaiyakha [Look here](https://godbolt.org/z/r3W7bxGvf). Now [look here](https://godbolt.org/z/svcbbc3ja). That is using MSVC to compile -- see the difference in the compiler output when `/WX` is used as the compiler option? You have `/WX` set. Are you aware of that? – PaulMcKenzie Aug 15 '22 at 00:51
  • You're causing undefined behaviour due to accessing value of uninitialised `int`. Undefined behaviour does not mean any guarantee of "random garbage". It means any outcome is possible, including the appearance of random garbage, or anything you consider to not be random garbage. Practically, in your case, the values you're seeing a probably whatever values happened to be at the relevant memory location when you access the value. – Peter Aug 15 '22 at 00:54
  • So, your question lists code that works and describes how to change it so it doesn’t work? Show the code you’ve asking about, – Pete Becker Aug 15 '22 at 01:13
  • *Why doesn't a default constructor work when I instantiate a class on the stack?* The default constructor **does** work when instantiated automatically (on the stack) or dynamically (on the heap). The problem is that the constructor hasn't initialized `x`, which is very useful in uncommon situations, and likely to cause **undefined behavior** in very common situations. – Eljay Aug 15 '22 at 12:21

1 Answers1

7

It's because x is uninitialized. Reading uninitialized variables makes your program have undefined behavior which means it could stop "working" any day. It may also do something odd under the hood that you don't realize while you think everything is fine.

These all zero-initialize x:

Foo* b = new Foo();
Foo* b = new Foo{};
Foo b{};

while these don't:

Foo *b = new Foo;
Foo b;

MSVC may not catch and warn about all cases where you leave a variable uninitialized and read it later. It's not required to do so - which is why you may get the warning in one case but not the other.

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • @TedLyngmo AFAIK `default` constructor must ensure all POD members are zero-initialised, see the [referenced post](https://stackoverflow.com/a/23698999/14755500) – Kaiyakha Aug 14 '22 at 23:49
  • @TedLyngmo when I use `Foo() = default` and allocate an instance on the heap, the output always gives me zeros. If I do the same thing but use `Foo() {}` instead I see random garbage. – Kaiyakha Aug 14 '22 at 23:53
  • @Kaiyakha Sorry, I missed your `()` after `Foo` Updated the answer – Ted Lyngmo Aug 14 '22 at 23:54
  • @Kaiyakha Beware that `= default` does _not_ cause the default constructor to initialize the member. It merely causes the specific initializer `()` (called _value-initialization_) to perform zero-initialization. It is not the constructor performing this action, which is why it doesn't apply if you don't use any initializer. – user17732522 Aug 15 '22 at 00:03
  • Huh. Isn't `Foo* b = new Foo();` synonymous to `Foo* b = new Foo;`? Is the 0-initialization actually guaranteed by the standard? Looks to me more like some optimization which randomly triggers because undefined behavior allows it to. – Piotr Siupa Aug 15 '22 at 00:03
  • @NO_NAME Zero-initialization in this case is specific to `()`. See my comment above. (A similar rule applies for `{}` and it will also result in zero-initialization of the member, although the exact details changed in the standard versions.) – user17732522 Aug 15 '22 at 00:03
  • @user17732522 True,. We could just remove `Foo() = default` from this example. No difference. – Ted Lyngmo Aug 15 '22 at 00:04
  • 1
    @NO_NAME No, `new Foo();` when the constructor is defauted (not user-provided) will actually zero-initialize the members while `new Foo;` won't. It's similar to `int a;` / `new int;` vs . `int a{};` / `new int{};` – Ted Lyngmo Aug 15 '22 at 00:07
  • 2
    The issue is that too many new programmers use observable behavior of their programs to figure out the rules of C++. That is not a valid way to figure things out. The rules of C++ are defined in the language rules, not by how your program behaves. The compiler's default constructor isn't going to waste time initializing your member variables *in an optimized build* -- if it did that, get ready for the mayhem the C++ community will bring onto the compiler vendor. – PaulMcKenzie Aug 15 '22 at 00:57