Why do some const variables referring to some exported const variables get the value 0?
-
06-09-2019 - |
Question
Consider the following. I have two exported constants as follows:
// somefile.h
extern const double cMyConstDouble;
extern const double cMyConstDouble2;
and
// somefile.cpp
const double cMyConstDouble = 3.14;
const double cMyConstDouble2 = 2.5*cMyConstDouble;
These constants are now referenced some place else to define two static (locally visible) constants:
// someotherfile.cpp
#include "somefile.h"
static const double cAnotherDouble = 1.1*cMyConstDouble;
static const double cAnotherDouble2 = 1.1*cMyConstDouble2;
printf("cAnotherDouble = %g, cAnotherDouble2 = %g\n",
cAnotherDouble, cAnotherDouble2);
Which yields the following output:
cAnotherDouble = 3.454, cAnotherDouble2 = 0
Why is the second double 0? I'm using .NET 2003 C++ compiler (13.10.3077).
Solution
Because cMyConstDouble is declared as extern, compiler is not able to assume its value and does not generate a compile time initialization for cMyConstDouble2. As the cMyConstDouble2 is not compile time initialized, its order of initialization relative to cAnotherDouble2 is random (undefined). See static initialization fiasco for more information.
OTHER TIPS
I'm not going to dip my toe into the issues of extern here, but why do you simply not place the consts in the appropriate header files and forget about "exporting" them using extern? This is how consts are supposed to be used in C++, and why they have internal linkage.
In other words:
// someheader.h
const double cMyConstDouble = 3.14;
const double cMyConstDouble2 = 2.5*cMyConstDouble;
and #include that file wherever you need them.
This is dangerous thing to do as your one static variable in one source file depends upon the another static variable in another cpp file. Check static initialization fiasco for more information.
If you change the initialization of cMyConstDouble2
to this here:
const double cMyConstDouble2 = 2.5*3.14;
Then your program should behave correct. The reason for this is that variables that
- Have POD type
- Are initialized with constant expressions (1)
are initialized at static initialization time. These initializations include
- Zero initialization of all objects having static storage duration
- Initializations of PODs initialized with constant expressions
Of your shown variables, only cMyConstDouble
satisfies both conditions of being fully initialized at static initialization time. However, cMyConstDouble2
does not, since its initializer does not satisfy the requirements of a constant expression. In particular, it includes a variable that doesn't have integral type (here, it has floating point type). However, floating point literals are allowed in arithmetic constant expressions. That is why 2.5*3.14
is an arithmetic constant expression. And that is why changing the initializer to that will require it to be statically initialized.
What will happen with cMyConstDouble2
if you stay with the non-constant expression? The answer is, you don't know. The Standard allows that variable to be statically initialized, but does not require it to do so. In your case, it was dynamically initialized - thus its value just after static initialization time was still zero. To get a feeling for how complicated that is, here is an example:
inline double fd() { return 1.0; }
extern double d1;
double d2 = d1; // unspecified:
// may be statically initialized to 0.0 or
// dynamically initialized to 1.0
double d1 = fd(); // may be initialized statically to 1.0
If the dynamic initialization doesn't change any other static storage variable (satisfied in your code) and when the static initialization would produce the same value as would be produced by dynamic initialization when all objects not required to be statically initialized would be initialized dynamically (also satisfied in your code) - then the variable is allowed to be initialized statically. These two conditions are also satisfied in the above code for both variables d2
and d1
:
Analysis of d2
= d1
does not change any other static storage variable- When both
d2
andd1
are initialized dynamically, thend2
would be initialized to0.0
, becaused2
is defined befored1
, and dynamic initialization ofd2
would grab the value ofd1
as of the state just after static initialization (where only zero initialization ofd1
took place).
Analysis of d1
= fd()
does not change any other static storage variable- When both
d2
andd1
are initialized dynamically, then= fd()
will initialized1
to1.0
.
So, the compiler may initialize d1
statically to 1.0
, because both conditions for optional-static-initialization are met.
If the compiler decides to initialize
d1
andd2
dynamically, thend2
will be initialized to0.0
, since it will grab the value ofd1
as it was just after zero initialization.However, if the compiler decides to initialize
d1
statically andd2
dynamically, thend2
will be initialized to1.0
, since the dynamic initialization ofd2
will grab the fully initialized value ofd1
as it was just after static initialization.
I'm not sure what the value of d2
is when d1
and d2
is initialized statically, though. That is, whether d2
is supposed to grab the 0.0
or the 1.0
, since there is no order defined for static initialization.
(1) Constant expressions include arithmetic constant expressions too (not only integral constant expressions), when considering initialization order of objects with static storage duration.