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.
     
    Steve Folly, Apr 20, 2007
    #1
    1. Advertisements

  2. An #include is probably missing..
    ;
    Another missing #include here...
    .... an unfinished sentence?
    No, zero it should have been.
    Most likely. For example, the 'Math.Pi' should actually be a function

    namespace Math {
    double Pi() { return 3.14159...; }
    }
    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
     
    Victor Bazarov, Apr 20, 2007
    #2
    1. Advertisements

  3. Steve Folly

    Steve Folly Guest

    Yep, sorry, was in a hurry to post!
    .... "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!)
    That'd be 'Maths' - I'm in the UK... ;-)
    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).
    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

    That problem normally only affects types with non-trivial
    constructors. Static initialization is guaranteed to take place
    before dynamic.
    No problem: static initializatino.
    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.
    The same comments applies to the other constants, of course.

    [...]
    No. Objects with static lifetime are guaranteed to be
    initialized with 0 (converted to the proper type).
    That's the classical solution.
    I doubt that there's really much difference between an
    inline function and a const variable defined in another
    translation unit.
    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.
    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.)
    No. Since the non-functions all have integral type.
     
    James Kanze, Apr 20, 2007
    #4
    1. Advertisements

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.