The "static initialization order fiasco" and constants

Discussion in 'C++' started by Steve Folly, Apr 20, 2007.

  1. Steve Folly

    Steve Folly Guest

    Hi,

    I had a problem in my code recently which turned out to be the 'the "static
    initialization order fiasco"' problem
    (<http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.12>)

    The FAQ section describes a solution using methods returning references to
    static objects.

    But consider:

    Maths.h:

    namespace Maths
    {
    const double Pi = 3.14159265358979323846;

    const double DegreesToRadians = Pi / 180.0;
    const double RadiansToDegrees = 1.0 / DegreesToRadians;

    const double RadiansToThousandthsOfMinutes = 180.0 / Pi * 60.0 * 1000.0;

    const double FeetToMetres = 0.3048;
    const double MetresToFeet = 1.0 / FeetToMetres;
    }


    Foo.cpp:

    namespace Foo
    {
    const double x = Bar::TwoPi();
    }

    Bar.h:

    namespace Bar
    {
    double TwoPi()
    }

    Bar.cpp:

    namespace Bar
    {
    double TwoPi()
    {
    return Maths.Pi * 2.0;
    }
    }


    (My actual problem was slightly more complex than this, but this is


    My problem arose because Maths::pi had not been initialised before Foo::x,
    Foo::x was equal to zero. Probably lucky to be zero at all, could have been
    anything I guess?

    The FAQ way to solve this would be to change the constants to functions?

    I don't want to change them to macros, but the thought of having to change
    these into functions just seems... I dunno... overkill just for the sake of
    several constants? (Especially when quite a lot of code uses these
    constants, and up until now I think we've been *extremely* lucky!)

    Are functions my best way out of this predicament?

    The thought occurs that members of numeric_limit<> classes are faced with
    the same problem? Is there still the danger here that using numeric_limit<>
    static members might not be initialized themselves when used to initialize
    other static data?

    Any advice appreciated.

    --
    Regards,
    Steve
     
    Steve Folly, Apr 20, 2007
    #1
    1. Advertising

  2. Steve Folly wrote:
    > I had a problem in my code recently which turned out to be the 'the
    > "static initialization order fiasco"' problem
    > (<http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.12>)
    >
    > The FAQ section describes a solution using methods returning
    > references to static objects.
    >
    > But consider:
    >
    > Maths.h:
    >
    > namespace Maths
    > {
    > const double Pi = 3.14159265358979323846;
    >
    > const double DegreesToRadians = Pi / 180.0;
    > const double RadiansToDegrees = 1.0 / DegreesToRadians;
    >
    > const double RadiansToThousandthsOfMinutes = 180.0 / Pi * 60.0 *
    > 1000.0;
    >
    > const double FeetToMetres = 0.3048;
    > const double MetresToFeet = 1.0 / FeetToMetres;
    > }
    >
    >
    > Foo.cpp:


    An #include is probably missing..

    >
    > namespace Foo
    > {
    > const double x = Bar::TwoPi();
    > }
    >
    > Bar.h:
    >
    > namespace Bar
    > {
    > double TwoPi()

    ;

    > }
    >
    > Bar.cpp:


    Another missing #include here...

    >
    > namespace Bar
    > {
    > double TwoPi()
    > {
    > return Maths.Pi * 2.0;
    > }
    > }
    >
    >
    > (My actual problem was slightly more complex than this, but this is


    .... an unfinished sentence?

    >
    >
    > My problem arose because Maths::pi had not been initialised before
    > Foo::x, Foo::x was equal to zero. Probably lucky to be zero at all,
    > could have been anything I guess?


    No, zero it should have been.

    > The FAQ way to solve this would be to change the constants to
    > functions?


    Most likely. For example, the 'Math.Pi' should actually be a function

    namespace Math {
    double Pi() { return 3.14159...; }
    }

    > I don't want to change them to macros, but the thought of having to
    > change these into functions just seems... I dunno... overkill just
    > for the sake of several constants? (Especially when quite a lot of
    > code uses these constants, and up until now I think we've been
    > *extremely* lucky!)
    >
    > Are functions my best way out of this predicament?


    Sure.

    > The thought occurs that members of numeric_limit<> classes are faced
    > with the same problem? Is there still the danger here that using
    > numeric_limit<> static members might not be initialized themselves
    > when used to initialize other static data?


    Actually, the compiler is allowed to do magic tricks to ensure that any
    of objects from the library are initialised in the proper order (like
    'std::cout', for example), so library are not subject to static object
    initialisation fiasco. We as users are not so lucky.

    V
    --
    Please remove capital 'A's when replying by e-mail
    I do not respond to top-posted replies, please don't ask
     
    Victor Bazarov, Apr 20, 2007
    #2
    1. Advertising

  3. Steve Folly

    Steve Folly Guest

    On 20/4/07 19:46, in article f0b1r0$3ta$, "Victor Bazarov"
    <> wrote:

    >> Foo.cpp:

    >
    > An #include is probably missing..


    Yep, sorry, was in a hurry to post!

    >> (My actual problem was slightly more complex than this, but this is

    >
    > ... an unfinished sentence?


    .... "My actual problem was slightly more complex than this, but this is a
    cut down minimal example." (So minimal it's even missing the #includes!)

    > namespace Math {
    > double Pi() { return 3.14159...; }
    > }


    That'd be 'Maths' - I'm in the UK... ;-)

    >> Are functions my best way out of this predicament?

    >
    > Sure.


    I suppose if I 'inline' them an optimizing compiler could optimize away the
    overhead of the function call? (We use Visual Studio 2005; I'll investigate
    this).

    > Actually, the compiler is allowed to do magic tricks to ensure that any
    > of objects from the library are initialised in the proper order (like
    > 'std::cout', for example), so library are not subject to static object
    > initialisation fiasco. We as users are not so lucky.


    Boo! Not fair!


    Very helpful. Thanks for your prompt reply Victor.

    --
    Regards,
    Steve

    "...which means he created the heaven and the earth... in the DARK! How good
    is that?"
     
    Steve Folly, Apr 20, 2007
    #3
  4. Steve Folly

    James Kanze Guest

    On Apr 20, 8:32 pm, Steve Folly <> wrote:

    > I had a problem in my code recently which turned out to be the
    > 'the "static initialization order fiasco"' problem
    > (<http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.12>)


    That problem normally only affects types with non-trivial
    constructors. Static initialization is guaranteed to take place
    before dynamic.

    > The FAQ section describes a solution using methods returning
    > references to static objects.


    > But consider:


    > Maths.h:


    > namespace Maths
    > {
    > const double Pi = 3.14159265358979323846;


    No problem: static initializatino.

    > const double DegreesToRadians = Pi / 180.0;


    The problem here is the "variable" Pi. Basically, the
    standard requires initialization with constant expressions
    to occur before any dynamic initialization. It then defines
    integral constant expressions (which allow for "const
    variables and static data members of integral or enumeration
    types initialized wuith constant expressions"); it then goes
    on to define other constant expressions (which can only be
    used for the purpose of non-local static object
    initialization), amongst which arithmetic constant
    experssions: according to the standard (§5.19/3):

    An arithmetic constant expression shall satisfy the
    requirements for an integral constant expression, except
    that

    -- floating literals need not be cast to integral or
    enumeration type, and

    -- conversions to floating point types are permitted.

    Note that your expressions do not qualify, because they
    contain a const variable which is *not* of integral or
    enumeration type.

    This looks like an oversight to me. If:

    const double DegreesToRadians = 3.14159265358979323846 / 180.0;

    requires static initialization, I don't see why your
    expression shouldn't. (Historically, of course, C didn't
    allow the use of const variables in this context.) On the
    other hand... the precision used in floating point
    arithmetic like the above is not specified---all that is
    guaranteed is that it is at least as much as a double.
    Whereas when you assign to a variable, the precision is
    guaranteed to be exactly that of the type of the variable.
    So that allowing const variables would require that a cross
    compiler emulate exactly the floating point of the target
    machine; the above, however, only requires some floating
    point of as much or greater precision.

    > const double RadiansToDegrees = 1.0 / DegreesToRadians;
    > const double RadiansToThousandthsOfMinutes = 180.0 / Pi * 60.0 * 1000.0;


    > const double FeetToMetres = 0.3048;
    > const double MetresToFeet = 1.0 / FeetToMetres;
    > }


    The same comments applies to the other constants, of course.

    [...]
    > My problem arose because Maths::pi had not been initialised before Foo::x,
    > Foo::x was equal to zero. Probably lucky to be zero at all, could have been
    > anything I guess?


    No. Objects with static lifetime are guaranteed to be
    initialized with 0 (converted to the proper type).

    > The FAQ way to solve this would be to change the constants to functions?


    That's the classical solution.

    > I don't want to change them to macros, but the thought of having to change
    > these into functions just seems... I dunno... overkill just for the sake of
    > several constants? (Especially when quite a lot of code uses these
    > constants, and up until now I think we've been *extremely* lucky!)


    I doubt that there's really much difference between an
    inline function and a const variable defined in another
    translation unit.

    > Are functions my best way out of this predicament?


    Probably. Inline functions will also optimize better, since
    the compiler will be able to see the actual value in all of
    the translation units.

    Otherwise, you could define the values as macros (using only
    floating point literals and other macros) in the compilation
    unit which defines the variables, something along the lines
    of:

    namespace Maths
    {
    #define PI 3.14159265358979323846
    const double Pi = PI ;
    #define DEGREES_TO_RADIANS PI / 180.0
    const double DegreesToRadians = DEGREES_TO_RADIANS ;
    // ...
    }

    Since the macros wouldn't be in a header, the namespace
    polution is limited.

    > The thought occurs that members of numeric_limit<> classes are faced with
    > the same problem?


    Are they? If you look at them carefully, you'll see that
    the "constants" which are not necessarily of integral type
    (i.e. whose type depends on the instantiation) are in fact
    functions. Probably for this very reason. (Although
    frankly, a good implementation could arrange for the values
    to be expressed as literals. I think it's more a means of
    allowing an exact bit pattern to be specified for floating
    point values. Something along the lines of:

    template<>
    double
    numeric_limits< double >::max()
    {
    static unsigned char r[] = { 0x7F, 0xEF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF } ;
    return *reinterpret_cast< double* >( r ) ;
    }

    This was probably felt to be more reliable than trying to
    express it as a decimal literal with type double.)

    > Is there still the danger here that using numeric_limit<>
    > static members might not be initialized themselves when used to initialize
    > other static data?


    No. Since the non-functions all have integral type.

    --
    James Kanze (Gabi Software) email:
    Conseils en informatique orientée objet/
    Beratung in objektorientierter Datenverarbeitung
    9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
     
    James Kanze, Apr 20, 2007
    #4
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Flip
    Replies:
    2
    Views:
    321
  2. Boris
    Replies:
    5
    Views:
    1,076
    Fei Liu
    Apr 19, 2006
  3. anon
    Replies:
    0
    Views:
    429
  4. S Claus
    Replies:
    6
    Views:
    2,202
  5. S Claus
    Replies:
    6
    Views:
    507
Loading...

Share This Page