The "static initialization order fiasco" and constants

S

Steve Folly

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.
 
V

Victor Bazarov

Steve said:
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
 
S

Steve Folly

An #include is probably missing..

Yep, sorry, was in a hurry to post!
... 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... ;-)

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?"
 
J

James Kanze

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:

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.
 

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. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,009
Latest member
GidgetGamb

Latest Threads

Top