4

I was playing with my C++ compiler and I noticed a recursive dependency of a global variable and a function call is not rejected. Example:

#include <iostream>

int foo();

int t = foo();

int foo()
{
    std::cout << "Hello before main: " << t << "\n";
    t = 10;
    return t + 10;
}

int main()
{
  std::cout << "Hello from main.\n";
}

This compiled program prints out the following:

Hello before main: 0
Hello from main.

So when I use declare the t it has a dependency on the foo function which again has a dependency on t. The C++ compiler breaks this loop by essentially having t be zero initialized before the assignment expression runs.

This was surprising to me. Is this correct behavior according to the standard or am I invoking some undefined behavior here?

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
CryptoNoob
  • 469
  • 3
  • 8

1 Answers1

4

Since C++20, the lifetime of object is considered to begin when its initializations finish; before that access to it leads to UB.

[basic.life]/1:

... The lifetime of an object of type T begins when:

  • storage with the proper alignment and size for type T is obtained, and
  • its initialization (if any) is complete (including vacuous initialization) ...

Until C++20 this was well-defined behavior. Zero initialization is performed at first for non-local variables.

As described in non-local initialization, static and thread-local variables that aren't constant-initialized (since C++14) are zero-initialized before any other initialization takes place. If the definition of a non-class non-local variable has no initializer, then default initialization does nothing, leaving the result of the earlier zero-initialization unmodified.

From the standard, [basic.start.static]/2

If constant initialization is not performed, a variable with static storage duration ([basic.stc.static]) or thread storage duration ([basic.stc.thread]) is zero-initialized ([dcl.init]). Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization. All static initialization strongly happens before ([intro.races]) any dynamic initialization.

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • @LanguageLawyer I'm not quite sure but does that mean that the lifetime of `t` begins only when all the initializations (including zero-initialization and copy-initialization for this case) finish? – songyuanyao Apr 16 '20 at 03:53
  • Currently it reads as that accessing an object before its dynamic initialization is completed is UB, but IIUC this is considered a defect. – Language Lawyer Apr 16 '20 at 04:49