Connecting Tech Pros Worldwide Forums | Help | Site Map

Initialization of static class variables. Difference between POD and std::string

Newbie
 
Join Date: Apr 2007
Posts: 1
#1: Apr 5 '07
I have a loooong debugging session behind me! I finally found the reason for the problem and now would like to know, if it is a bug in my code or not standardconformant behavour of the compiler(s) or not a bug at all and just normal behavior:
This simple sample program illustrates the problem. Please use separate header (.h) and code (.cpp) files, because it plays a role in the problem...
Expand|Select|Wrap|Line Numbers
  1. /**************************************
  2.  * Module:  TCStaticmember.h
  3.  **************************************/
  4. #if !defined(TCStaticmember_h)
  5. #define TCStaticmember_h
  6. #include <string>
  7.  
  8. class TCStaticmember
  9. {
  10. public:
  11.    static std::string staticMember;
  12.    static int staticMemberPOD;
  13.  
  14. protected:
  15. private:
  16. };
  17. //move the definition of staticMember here to get it working
  18. #endif
  19.  
  20. /**************************************
  21.  * Module:  TCStaticmember.cpp
  22.  **************************************/
  23. #include "TCStaticmember.h"
  24.  
  25. //move the following definition to TCStaticmember.h to get it working
  26. std::string TCStaticmember::staticMember="I'm the value of a static std::string variable!";
  27. int TCStaticmember::staticMemberPOD=2;
  28.  
  29. /**************************************
  30.  * Module:  TStaticMemberUser.h
  31.  **************************************/
  32. #if !defined(TStaticMemberUser_h)
  33. #define TStaticMemberUser_h
  34.  
  35. #include "TCStaticmember.h";
  36. #include <string>
  37.  
  38. class TStaticMemberUser
  39. {
  40. public:
  41.    TStaticMemberUser();
  42.  
  43.    std::string mystring;
  44.    int myint;
  45.  
  46. protected:
  47. private:
  48. };
  49.  
  50. #endif
  51.  
  52. /**************************************
  53.  * Module:  TStaticMemberUser.cpp
  54.  **************************************/
  55. #include "TCStaticmember.h"
  56. #include "TStaticMemberUser.h"
  57.  
  58. TStaticMemberUser::TStaticMemberUser()
  59. {
  60.    mystring = TCStaticmember::staticMember;
  61.    myint = TCStaticmember::staticMemberPOD;
  62. }
  63.  
  64. /**************************************
  65.  * Module:  main.cpp
  66.  **************************************/
  67.  #include "stdio.h"
  68. #include "TStaticMemberUser.h"
  69. TStaticMemberUser StaticMemberUser;
  70. int main(int argc, char* argv[])
  71. {
  72.     printf ("The value of the static std::string variable is: %s\n",StaticMemberUser.mystring.c_str());
  73.     printf ("The value of the static POD (int) is: %d\n",StaticMemberUser.myint);
  74.     return 0;
  75. }
I built this program using 3 different compilers/linkers:
Borland compiler and linker included in C++ Builder 6.0
Microsoft compiler and linker included in Visual C++ 6.0
GNU compiler and linker (gcc version 3.4.2 (mingw-special))

The GNU compiler builds the program without any problems, but does never enter the main function in debugging mode and crashes in normal mode (GPF)

The Borland and Microsoft Compiler both generate the following output:
Expand|Select|Wrap|Line Numbers
  1. The value of the static std::string variable is:
  2. The value of the static POD (int) is: 2
Why is the std::string variable not initialized, whereas the int is correctly set to 2?
In Borland C++ the std::string variable even can be in an undefined state leading to occasional system crashes at startup (the reason for my debugging session...).
The "solution" for the problem is strange in my view: I have to move the definition of the std::string variable from the TCStaticMember.cpp file to the TCStaticMember.h file (the locations to delete/paste are marked with bold comments in the code). With Microsoft I then have to use the -F (Force) linker flag because of duplicate definitions, but everything works as expected then...
Borland even does not complain about that and the output is as expected:
Expand|Select|Wrap|Line Numbers
  1. The value of the static std::string variable is: I'm the value of a static std::string variable!
  2. The value of the static POD (int) is: 2
I would really appreciate if someone could answer the question, if me, the compilers, the STL implementers or the standard definers did something wrong...

Thanks in advance...

JosAH's Avatar
Expert
 
Join Date: Mar 2007
Posts: 10,611
#2: Apr 5 '07

re: Initialization of static class variables. Difference between POD and std::string


You've hit the Static Initialization Fiasco.
The difference between constant ints and char*'s is that the compiler can put
that '2' in the initialized variable space used by the loader, i.e. there will no
instructions necessary to set that variable to the value 2. Not so with the the
char*, i.e. the linker determines where the string constant is stored in real
memory and the loader has to store that address in your static char* variable.

You can't know which globals are initialized first and when you use a
(presumably) initialized variable which actually hasn't been initialized yet: bingo,
you hit the Static Initialization Fiasco.

There's a way around it: initialize everything in one place and take care that
the entire compilation unit is initialized before main is called. I know it's clumsy
but that's the way it is with linkers who know zilch about C++ ;-)

kind regards,

Jos
Moderator
 
Join Date: Mar 2007
Location: North Bend Washington USA
Posts: 5,366
#3: Apr 5 '07

re: Initialization of static class variables. Difference between POD and std::string


This is just another example of why you should avoid global variables.

Fior a given source file you are guaranteed that the global variables are initialized in the order that they are declared. Just arrange them so the file compiles.

A good designer will put these guys in an anoymous namespace and access them only by functions in that same source file. Users will be required to call the functions rather than use the variables directly.

The last thing you want is the names of your global variables scattered all over the code (the Static Initialization Fiasco). Should you need to redesign, you will have very large job. By using an accssor function all you need to do is change the function (keeping the prototype the same) and recompiling. The users need only re-link.
Reply


Similar C / C++ bytes