views:

280

answers:

4

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

+9  A: 

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.

anon
So, you *are* dipping your toe in then? :-) +1 anyway.
paxdiablo
Not really - my solution doesn't use extern.
anon
Actually there is a reason why. We moved from your way to this way, because they caused enormous linking times.
Magnus Skog
Then there is something wrong with the architecture of your codebase. All the C++ code I have written and reviewed for the past decade uses my way of doing it with absolutely no problems.
anon
I'm definitely going to consider your opinions. Thanks for your reply.
Magnus Skog
@Neil, definitely, i would use that way too. +1 :)
Johannes Schaub - litb
Also, using "extern const" on integral types will force C-style linkage, which is probably not desired.
Shmoopty
+7  A: 

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.

Suma
@Suma: If the `cMyConstDouble2` is not constant folded due to `cMyConstDouble` being `extern` shouldn't the same happen to `cAnotherDouble`? This too depends on `cMyConstDouble` right? How come it's value is printed rightly as 3.454
legends2k
Order of initialization across modules is undefined. In one case it was already done, in the second not.
Suma
+3  A: 

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.

Naveen
It is dangerous in general, but this does not apply when compile time initialization by constant takes place. My answer points to the fact that while the code may look like compile time initialization, in fact it is not because of extern.
Suma
+2  A: 

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 and d1 are initialized dynamically, then d2 would be initialized to 0.0, because d2 is defined before d1, and dynamic initialization of d2 would grab the value of d1 as of the state just after static initialization (where only zero initialization of d1 took place).

Analysis of d1

  • = fd() does not change any other static storage variable
  • When both d2 and d1 are initialized dynamically, then = fd() will initialize d1 to 1.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 and d2 dynamically, then d2 will be initialized to 0.0, since it will grab the value of d1 as it was just after zero initialization.

  • However, if the compiler decides to initialize d1 statically and d2 dynamically, then d2 will be initialized to 1.0, since the dynamic initialization of d2 will grab the fully initialized value of d1 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.

Johannes Schaub - litb