0

I am trying the following C++ code, which initializes member variables c1 and c2 based on the static variables v1 and v2 in different namespaces. It seems to me that cyclic dependency occurs for initialization of those variables.

#include <iostream>

namespace M1
{
    struct T1
    {
        int c1;
        T1();
    };

    T1 v1;
}

namespace M2
{
    struct T2
    {
        int c2;
        T2() { c2 = M1::v1.c1; }            // (1)
        // T2() : c2( M1::v1.c1 ) {}        // (2)
    };

    T2 v2;
}

M1::T1::T1() { c1 = M2::v2.c2; }            // (3)
// M1::T1::T1() : c1( M2::v2.c2 ) {}        // (4)


int main()
{
    std::cout << M1::v1.c1 << std::endl;
    std::cout << M2::v2.c2 << std::endl;

    return 0;
}

However, if I compile this code with g++-5.3 (installed via homebrew on OSX 10.9), it always complies successfully (with no warning with -Wall) and gives

0
0

I also tried replacing lines (1) and (3) by (2) and (4), but no change in the results (i.e., 0 and 0). So I am wondering why this code works successfully. No cyclic dependency here? If not, is the result simply undefined? (i.e., garbage data in the memory for c1 and c2 printed?) I would appreciate any hints on this behavior. Thanks!

1 Answers1

2

Yes, you have a circular dependency as the values are initialised from one another.

There is no error for almost the same reason that you don't get an error in the following:

int x;
std::cout << x;

in that, logically, you're simply using uninitialised variables. The value would be unspecified and the compiler can't detect this in the general case, so doesn't in any case.

However, we must also recall that objects of static storage duration are zero-initialised before practically anything else happens, so your program is actually well-defined.

It means that M2::v2.c2 will assuredly be initialised to zero; as a consequence, M1::v1.c1 will then also be initialised to zero.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • Hmm, I'm still very confused... This SO page http://stackoverflow.com/questions/7207884/class-construction-with-initial-values seems to explain that primitive types like `int` do not have pre-defined values for initialization... Is this not correct, and in fact always zero-initialized? (unless other assignment occurs?) (Please see the Appendix "1" in the Answer.) –  May 05 '16 at 11:45
  • @spectrum: The page is correct, but it talks about class member variables. You are using global variables. The two things are not the same. – Lightness Races in Orbit May 05 '16 at 11:47
  • @spectrum The link is about objects with *automatic storage duration*, which are by default uninitialized for primitive types. Zero-initialization happens for *static storage duration*, like, in your case, global variables. – lisyarus May 05 '16 at 11:49
  • My another confusion is, in Line (3), even if the construction of v2 has not been finished, is v2.c2 used for the assignment for v1.c1? (i.e., referring only to the member variable c2 and disregarding other members (if any))? I'm still learning C++ so maybe lacking basic knowledge... (sorry if this is too basic). –  May 05 '16 at 11:50
  • @spectrum: _"even if the construction of v2 has not been finished, is v2.c2 used for the assignment for v1.c1?"_ That can't happen. You only have one thread. – Lightness Races in Orbit May 05 '16 at 11:56
  • I guess I'm not understanding the difference of initialization between global and automatic objects. In the global-variable case, is each member (of primitive type) initialized to zero at the start-up of the program, while no default constructor called (i.e., T1() and T2() not called)? Or are they still called with already zero-initialized members? (The latter seems more plausible...) –  May 05 '16 at 12:09
  • @spectrum: The latter. They are zero-initialised as a first step during startup of your program. All the usual initialisation then happens after that. It's a bit weird. I think it's basically the standard's way of saying "look, in reality everything in that block of memory is going to be memset to zero to begin with, and I'm now guaranteeing that for 'convenience'". – Lightness Races in Orbit May 05 '16 at 12:11
  • Hmm, I see... It seems very natural that the memory of the static variables is first filled in with zero (for int), and then used in any subsequent function. I also tried inserting "cout" in the two constructors, and they indeed print test messages (i.e., constructors are called for globals as well). Now I feel more consistent. Thanks very much :) –  May 05 '16 at 12:18