C++ global variable accessible to multiple files

Discussion in 'Webmastering & Programming' started by CefiroZ, Apr 26, 2007.

  1. CefiroZ

    CefiroZ Limp Gawd

    Messages:
    218
    Joined:
    Jan 17, 2004
    Okay this is a really dumb question but I can't seem to come up with a satisfactory way to do the following.

    I need a (preferably constant) variable accessible to my entire C++ project. That is, I have many .cpp files that need to use the same variable. I've tried all kinds of combinations of extern, #define, static (within the best class to dump them in), separate files to only define them, etc that I've found around the internet and nothing has worked. The ideal solution would be have a header file that I could just #include wherever I needed access to the variables. This should be simple, but for some reason how to do this is escaping me right now.

    Thanks in advance!
     
  2. FreiDOg

    FreiDOg [H]ardness Supreme

    Messages:
    4,456
    Joined:
    Jul 7, 2001
    in C++ if you want to extern a const you need to declare the 'primary' instance as extern as well. By default const variables are not accessible to the linker, using extern overrides that.

    e.g.,
    extern const int i = 1; /*this declares and allocates memory for integer i and sets it equal to 1. You must only use this once, more than that an you create multiple instances of i that are accessible to the linker */

    extern const int i; /*this references a const integer i allocated in a different object file. Use this where ever you need access to i */
     
  3. jimmyb

    jimmyb 2[H]4U

    Messages:
    3,172
    Joined:
    May 24, 2006
    When I have this problem I usually just make a new class that has static variables. Then it's a simple matter of including it like you suggested.

    I have no idea if this is the best method though (or even a good one). I've also used singletons for similar situations as well.
     
  4. CefiroZ

    CefiroZ Limp Gawd

    Messages:
    218
    Joined:
    Jan 17, 2004
    Thanks guys, both of those methods work great!
     
  5. CodeX

    CodeX 2[H]4U

    Messages:
    3,006
    Joined:
    Mar 21, 2006
    It its constant, just throw it into a header file called constants along with all your other constants and include it in every cpp file. Constants do not use memory when the program is running, the compiler treats them as #define's where the expression is simply replaced with its literal definition everywhere it is found in the code.

    That does not allocate any memory at all, everywhere the (in)variable i is used in the code it is replaced with a 1 at compile time.
     
  6. mikeblas

    mikeblas [H]ard|DCer of the Month - May 2006

    Messages:
    12,858
    Joined:
    Jun 26, 2004
    const declared variables may or may not use memory, and are not treated as preprocessor macros. Further, having const definitions in a header which is included multiple times in a program violates the single definition rule.

    For these reasons, it's a very bad practice to "just throw" const declarations into a header file and hope that the compiler and linker sort out your mess for you.
    It may, or it may not, take memory. This is an implementation detail left to the compiler, but the compiler is influenced by the use of the symbol. In this example:

    Code:
    
    #include <stdio.h>
    
    const int i = 1;
    
    int main()
    {
       printf("address is %p\n", &i);
       return 0;
    }
    
    the constant certainly does get a memory location and does take up four bytes of space. The issue is vast exacerbated when the suggestion of creating a header with the constants is followed. const declarations are implicitly static, and thus they're not combined by the linker, resolved by the compiler, and so on. The results can be quite surprising for people unfamiliar with the language. Let's consider this small program:

    Code:
    // file foo.cpp
    
    #include <stdio.h>
    #include "common.h"
    
    extern void That();
    
    void This()
    {
       printf("In This, address is %p\n", &i);
    }
    
    int main()
    {
       This();
       That();
    }
    Code:
    // that.cpp
    #include <stdio.h>
    #include "common.h"
    
    void That()
    {
       printf("In That, address is %p\n", &i);
    }
    
    Code:
    // common.h
    const int i = 1;
    
    When run, here's what it printed on my machine:
    Code:
    D:\>foo
    In This, address is 0040A160
    In That, address is 0040A17C
    
    Now, we've found that the single constant has taken not only four bytes of memory -- but there are two instances of it and it takes eight bytes! It's not uncommon to make tables of structures or strings in constant data, and use those arrays to drive other code. Those constructs might be very large; a table of error messages, or ratios, and so on, say. "Just tossing" them into a header in a large program with many compilands can make the program bloat substantially as the declaration is replicated all over the place.

    Worse yet, it can lead to erratic behaviour for anyone expecting a single instance of those objects, or using identity of the location. And it even robs performance at runtime; the vlaues are stored at different locations, which can interfere with the processors ability to cache them -- polluting the cache and further ejecting other values for the redundant declarations.
     
  7. nameless_centurian

    nameless_centurian Gawd

    Messages:
    836
    Joined:
    Apr 4, 2003
    i love when you do explanations like the preceding. it's what keeps me coming back.
     
  8. CefiroZ

    CefiroZ Limp Gawd

    Messages:
    218
    Joined:
    Jan 17, 2004
    @mikeblas

    So you've pointed out the problem with doing it the common header way, but I do not see a mention of any proper way to accomplish the task. Do you recommend the class method jimmyb suggests? Or something else?

    And, I agree with centurian, very much the reason why HardForum is so great!
     
  9. CodeX

    CodeX 2[H]4U

    Messages:
    3,006
    Joined:
    Mar 21, 2006
    Thanks for clearing that up mike, most of the information I have on topics like this comes from college professors, who obviously don't know as much as they should. I have been told over and over that constant declarations do not use memory and are handled by the preprocessor in the same way that defines are. I have also been taught that it is good practice to make a single header file for all of your constant declarations and just include it as needed, this is especially stressed by certain professors who are fanatic about not having any magic numbers (which I think is ridiculous, if I am looping for all of the degree angles in a circle, why do I need to define the min angle as 0 and the max angle as 359 when you can just put those values in, since they are obvious and will never change.)
     
  10. mikeblas

    mikeblas [H]ard|DCer of the Month - May 2006

    Messages:
    12,858
    Joined:
    Jun 26, 2004
    I'm sorry that you're upset that I didn't write up a complete treatise of the issue for you, Cefiro. It's quite deep, particularly when considering all the follow-up questions. I thought it was more important to present correceted information and get everyone on the right track.

    Further, I think you've misunderstood my post. I'm not saying anything is wrong with the common header approach. The problem is with creating multiple definitions of the same thing. And specifically, when using constants, expecting them never to take memory, and not cause problems when they do.

    I'm not sure I see the point to JimmyB's method of using a class. It just wraps the same problem in a class.

    FreiDOg has the right angle in his post. What goes into the header shouldn't be the constant definition; it should be a declaration saying the constant is going to be extern. It's fine to have a constants.h header; but the point is you really need a constants.cpp to go along with it.

    We can fix my example program like this; I don't declare a seperate common.cpp and just bake my single constant into the main file for brevity (because posting in forums like this is a real chore for formatting). Instead of having a definition of i, I make a declaration:

    Code:
    // common.h
    extern const int i;
    
    And then define i back in the main module:

    Code:
    // file foo.cpp
    
    #include <stdio.h>
    #include "common.h"
    
    extern void That();
    
    extern const int i = 1;	// definition here
    
    void This()
    {
       printf("In This, address is %p\n", &i);
    }
    
    int main()
    {
       This();
       That();
    }
    
    Now, I only have one definition in my binary image and I'm golden. Sure enough, when it's run, there's only one instance of i in the program no matter which module was looking for it:

    Code:
    C:\const>foo
    In This, address is 0040A158
    In That, address is 0040A158
    
    i still takes up memory, though; it's really an integer, stored as sizeof(int) bytes. This means that i won't be folded up like a constant. If someone does some math with i, the compiler will go off and get the value stored in the i variable. When compiling that.cpp, it doesn't know the value of i and has to get it from memory at runtime. If it did know the value of i at compile time, it might be able to do more optimizations.

    And that leads us to the tradeoff: If you really want something that behaves as a preprocessor macro, you should use a preprocessor macro. You can define it and use it wherever you wish, and it really is simply substituted by the preprocessor as a literal. You can have problems managing types (is #define ZERO 0 an integer, a float, a short, a long, unsigned?). And it simply doesn't work for more interesting types, like arrays or tables.

    It's flatly wrong to think that a const variable is handled in the same way as the compiler handles a preprocessor macro. The fact is, the preprocessor handles preprocessor macros and the compiler never gets involved at all! A const variable, however, has to be completely handled by the compiler.

    Preprocessor macros have the advantage that they're really literal: if you want DEGREES_IN_CIRCLE to be 360, then you really get "360" every time you reference it. You really do never take up memory--or, at least, you do know what memory you take up becase you're looking right at the expression that does it. The compiler can notice that DEGREES_IN_CIRCLE is even, and constant, and can unfold loops inovlving it, for example; or short-circuit comparisons and optimize them out, and so on. It might not be able to do these things with an extern const.

    Further, a const declaration may take up memory. It depends on the compiler. Cheap, simple compilers might always just emit a variable. More complicated compilers try to optimize for constant folding. In the end, you have to observe what it is your tool does and figure out what it is you wanted it to do -- and how to get what you want.

    In Visual C++, you'll find that simple definitions like "const int i = 3;" will indeed take no memory and be used as literal values, folded in everywhere they can be. That is, until someone takes the address of the variable. And if they have the address, they can actually cast away constness and change the value! This is why it's correct behaviour for a compiler to actually store these someplace, and reference them each time. What if they chagned?

    Again, in the current version Visual C++ -- other compilers, and other versions of VC++ might differ -- the compiler tries to store constants are stored in an initialized segment which is marked read-only at runtime. This is a legal, even though very bad, C++ program:

    Code:
    #include <stdio.h>
    
    const int i = 1;
    
    int main()
    {
    	printf("i == %d\n", i);
    
    	// here's a good way to impress your boss:
    
    	int* pi = (int*) &i;
    	*pi = 37;
    
    	// now the fun starts
    	printf("i == %d\n", i);
    
    	return 0;
    }
    constness is explcitily cast away. The program doesn't fail until runtime, though. The VC++ has asked the linker to get four bytes as an integer, initialize that integer to 1, and then store them in the protected, read-only static data segment. The linker complies, so when this program is in loaded, that memory appears and has the value 1 and it can't be changed. At the *pi= assignment, the code will trip a fault at runtime.

    Maybe other compilers don't do that; it's platform and implementation dependent. (And you can convince VC++ to behave that way, too, if you push the right options.)

    In the real world, these problems add up fast. When I was working at Microsoft, I shaved more than a megabyte off of a shipping product's executable by fixing up constant declarations so they weren't made redundant by the tool chain. I had to step carefully, because if I broke anything, the whole product could tumble down on me. But it worked out; and by forcing less cache use and better locality, I also realized a savings of a couple of tenths of percent in an important benchmrak.

    At my new job, I've found the same problem -- though not nearly as bad, since the code base is so much smaller and uses so much less const-initialize data tables.

    In fixing the issues, I usually don't bother fixing simple value declarations. Declaring an integer of any size is fine; the compiler will use it as a constant until someone takes the address of it. Mostly, getting the address of a constant isn't interesting, but it does happen.

    VC++ does not treat floating point values as foldable constants, though. So if you've got:

    Code:
    const double dPi = 355.0 / 113.0;
    const float fDegrees = 360.0;
    
    then you're going to find that the compiler does emit a variable.

    Note that you can also have the compiler create a const and initialize it from a function call:

    Code:
    const double dPi = 16 * atan(0.2); // is that right?
    const int nNumber = rand();
    
    Here, the compiler must evaluate the right-hand side of the initializer at runtime. So it must initialize the value at runtime, storing the result in read-write memory. It takes memory every time; every time it is referenced, a module will get code for the initializer and that code isn't shareable. Worse yet, if someone does cast away constness, they can dereference the value and write over it at runtime because protection isn't possible!

    Code:
    
    #include <stdio.h>
    #include <math.h>
    
    const double dPi = 16 * atan(0.2);
    
    int main()
    {
    	printf("dPi == %g\n", dPi);
    
    	double* pPi = (double*) &dPi;
    	*pPi = 500.3;
    
    	printf("dPi == %g\n", dPi);
    	return 0;
    }
    
    gives me:

    Code:
    C:\const>diddle2
    dPi == 3.15833
    dPi == 500.3
    
    with no complaints. Yikes!!1! Now the problem is getting pretty bad; we have constants, they take memory, if they're placed in a header, their initialization will emit code that runs before program start, and that code just multiplies the more places touch it.

    Doubles, and the code to initialize them, can get pretty big. One way to make it worse is if we started using strings:

    Code:
    const char szCity[] = "Sammamish";
    const char *pstrStreet = "East Lake Sammamish Parkway Northeast";
    
    Again, speaking for VC++, we end up with an instance of "Sammamish" in each module that references szCity. That's ten bytes, plus alignment padding, for each module.

    The pstrStreet initializer might not be so bad. It's declaring a pointer, not an array. The string can be stored off someplace that does get found and folded, but the pointer ends up being stored again and again.

    The next step is arrays; if we write an array of such initializers, we're screwed:

    Code:
    struct Places {
      char szCity[50];
      char szState[50];
    };
    
    const Places [] = {
      { "Monroeville", "Pennsylvania" },
      { "Windsor Locks", "Connecticut" },
      { "Schenectady", "New York" },
      { "Sammamish", "Washington" },
      { "Tokyo", "Japan" },
    };
    
    That really piles up. Now, we've got a big pile of data, initializers, and so on -- each replicated in every module carrying a reference to this code. It's awful!

    Let me know if you've got further questions.
     
  11. Gambit

    Gambit Gawd

    Messages:
    764
    Joined:
    Aug 26, 2002
    *Excellent* explanation mike. Thank you for taking the time to explain the problem / solution in detail. It's much easier to understand why it's a problem with the examples given.
     
  12. CefiroZ

    CefiroZ Limp Gawd

    Messages:
    218
    Joined:
    Jan 17, 2004
    Wow, thanks mike, very helpful! My undergrad mostly consisted of Java with only limited C++. I like using C++ a lot better, have a handle on the paradigms and STL, and can use it effectively. However, some of the deeper language issues like those discussed here I haven't really been exposed to. Would you (or anyone else) recommend any "advanced" books I could pick up on C++? What I know comes from one class with a very good professor with the book "C++ for Java programmers" and snippets I've found on the internet when trying to figure stuff out. Thanks again!
     
  13. mikeblas

    mikeblas [H]ard|DCer of the Month - May 2006

    Messages:
    12,858
    Joined:
    Jun 26, 2004
    With the semantics of the language out of the way, let's examine the ideas behind these coding conventions.

    There are some constants which probably are global; the page allocation size on the machine, for example, or the number of degrees in a circle. Some people like to use "Word constants", like "kOneThousand" for 1000, and "kMegabyte" for 1024*1024. Maybe there's the name of your company, too, or a copyright message, and so on.

    Tossing all of these values into a single global header file is probably okay. They're used everywhere, and they really are global.

    But putting "all of your constants" in to a header is probably a bad idea because constants, just like everything else, end up having scope. The number of iterations a loop has to run for a hash function to get something done in the bottom of a collection class is an implementation detail of that class. It shouldn't be in a header file that's everwhere in every project because that hurts encapsulation.

    Even things which might initially seem like good universal globals aren't, really. Should the number of degrees in a cricle really be in the global header file? Will the code that does memory management every need it? If not, then it probably should be declared at a lower scope.

    Classes and namespaces can help with this. CMemoryManager doesn't need to know about CTargetingSystem, so it should be obvious where the memory size and degrees-per-arc constants go (or can be found).

    I don't think of these things as "magic numbers". Largely, constants in code can be explained away with a commnet:

    Code:
    // point the turret in each direction to
    // see what we might be able to hit
    for (int n = 0; n < 360; n++)
    {
       pTurret->Aim(n);
       nTotalTargets += pTurret->VisibleTargets();
       // ...
    
    seems really self-explanatory, and hardly seems better than

    Code:
    // point the turret in each direction to
    // see what we might be able to hit
    for (int n = 0; n < kDegreesInCircle; n++)
    {
       pTurret->Aim(n);
       nTotalTargets += pTurret->VisibleTargets();
       // ...
    
    The "magic numbers" I usually see are truly magic: they're arbitrary, and repetitious. If you write lots of UI code, for instance, you'll often see addition of one or two or three to a coordinate to provide a border. Positioning a window next to the other window plus three doesn't make much sense, and you'll quickly grow tired of commenting it up:

    Code:
       // we use a three-pixel border between windows, plus a 5 pixel margin
       MoveWindow(pWindow, pOtherWindow->Left() + 3 + 5, pOtherWindow->Top() + 3 + 5);
    
       // did the mouse click on the window? we need to include the three-pixel
       // border in the hit test, as well as the 5-pixel margin.
       if ( nMouseX >= pOtherWIndow->Left() - 3 - 5 && nMouseX <= pOtherWindow->Right() + 3 + 5)
       {
          // yes! ...
    
    Since they're arbitrary because of aesthetics or changeable when someone skins the app or redesigns the look, it's easier to change them as constants. And you can give up the descriptions:

    Code:
       // move the window, miding the margins
       MoveWindow(pWindow, pOtherWindow->Left() + kWindowBorder + kWindowMargin, 
    		pOtherWindow->Top() + kWindowBorder + kWindowMargin );
    
    and so on.

    There are also truly magic numbers. If you implement a collection class, you'll have a method to let it grow. How much does it grow? By one unit? By ten percent? By the golden ratio? By double? This number is magic; it's arbitrary, but it defines your application's execution characteristics at a very low level. Seems like such a thing should be documented, and commented, and made into a constant within the class so that it's very, very obvious how to fix it, how it was chosen, and how it might (or shouldn't!) be changed. I'd hate to go and find constants.h just to diddle that; it should be right in YourCollectionClass.h, inside the class declaration.

    All that said, programming styles are really arbitrary. It's very funny to me that people will argue for days about variable capitalization, which dialect of Hungarian the want, and so on. They really should be writing docs about how to avoid problems like the one we're outlining in this thread. Once a team latches on to an idea like "just toss it into constants.h", they're jumping downhill at a set of problems they might not even be aware of. A conversation about that would be far more useful than an argument about whether "cCount" or "kCount" is better.
     
  14. mikeblas

    mikeblas [H]ard|DCer of the Month - May 2006

    Messages:
    12,858
    Joined:
    Jun 26, 2004
    I'm loathe to recommend books because every person learns differently, and each person has a different perspective. What I think is a great explanation of an advanced concept might be something you think is a terrible discussion of an obvious issue.

    It's also hard for me to recommend books because most of what I've learned come from working on the language team itself, or gaining experience by fixing customers' code. I learned by studying the language standard, working the implementation, and figuring out what goes wrong in practical applications. You're probably taking a different path.

    That said, the most noted books about these edges are by Scott Meyer. The "Effective Something About C++" books are pretty useful, and easily digestible.

    Some of the Herb Sutter books are good, too.
     
  15. CodeX

    CodeX 2[H]4U

    Messages:
    3,006
    Joined:
    Mar 21, 2006
    That was fascinating mike, I learned more from that than I do in a month at school
     
  16. Gambit

    Gambit Gawd

    Messages:
    764
    Joined:
    Aug 26, 2002
    The one book I've found really useful is Jamsa's C / C++ Bible. Reason being, it doesn't actually teach you much of anything but (IMO) is an excellent reference. I can look up file I/O and find a quick, short example or two. From there, I can go look up the function further or look up the class mentioned and go from there. On the flip side, books I can't stand are anything written by Deitel & Deitel
     
  17. mikeblas

    mikeblas [H]ard|DCer of the Month - May 2006

    Messages:
    12,858
    Joined:
    Jun 26, 2004
    I'm not really sure those books, or that kind of practice realizes CefiroZ goal of learning about more detailed issues.
     
  18. Gambit

    Gambit Gawd

    Messages:
    764
    Joined:
    Aug 26, 2002
    I'd be surprised if this subject *wasn't* included in Jamsa's book. It may not have discussed in detail as you have of *why* you should do it that way and not another, but I think few books really will. Those that do, may not put it in plain english. The Deitel comment was more because it makes me want to choke someone after reading it.

    Having said all that, I know you said you loathe recommending books, but consider what's being said. Obviously everyone in this thread likes your explanation and how you've described everything. I think having *any* recommendation from you would be better than nothing. I'd far rather take a chance on a book that someone (who I can understand and follow) said they found useful than pick one at random and hope for the best. If I take the recommended book and find it's not useful, I had the same chances of picking another book that would also be terrible. Basically, I don't see how it's a bad thing for someone to recommend a book they found useful.
     
  19. mikeblas

    mikeblas [H]ard|DCer of the Month - May 2006

    Messages:
    12,858
    Joined:
    Jun 26, 2004
    It's a bad thing because, if the book doesn't fulfill the need of the recommendation, it's a waste of time or money (or both).
     
  20. Gambit

    Gambit Gawd

    Messages:
    764
    Joined:
    Aug 26, 2002
    True, but who said you had to buy it? You can read it at a library. In either case, I can't imagine a book who's sole purpose would be to teach accessing global variables in multiple files. If you're looking for an answer to a quick topic, a forum such as this or a search on the web would suit a person better I think.

    Obviously people will know different things, learn in different ways and have different opinions of what makes a good book; that can't be avoided though. Is checking out a book first a good idea? Of course it is. Short of that, I'll take a recommended book over a non-recommended one any day.
     
  21. mikeblas

    mikeblas [H]ard|DCer of the Month - May 2006

    Messages:
    12,858
    Joined:
    Jun 26, 2004
    Large-Scale C++ Software Design by Lakos is a book that deals mostly with code factoring, including how to place variables and constants in headers. It also discusses how to decide when to create a header, how to structure header files, how to identify and manage smaller pieces of the bigger picture, and so on.

    The title is a bit misleading; these techniques are necessary for non-trivial project. Practicing on smaller projects is how developers get better, and growing smaller things is how projects get bigger.
     
  22. mikeblas

    mikeblas [H]ard|DCer of the Month - May 2006

    Messages:
    12,858
    Joined:
    Jun 26, 2004
    Thank you for your kind words.
     
  23. jimmyb

    jimmyb 2[H]4U

    Messages:
    3,172
    Joined:
    May 24, 2006
    Terrific read.

    Regarding the method I mentioned earlier of using static class members, I would have thought they only exist once in the compiled class object. Is this not the case?
     
  24. mikeblas

    mikeblas [H]ard|DCer of the Month - May 2006

    Messages:
    12,858
    Joined:
    Jun 26, 2004
    If the definition exists in a header, one potentially exists in every module that references that header. I'll have to look into it because the scope might give the compiler a little more information about the symbol -- the linkage might end up being default extern instead of default static. But I don't see how that wouldn't cause duplicate definition errors, since the consumer of the header has seen the declaration and therefore needs their own instance of it.

    The part of the One Definition Rule we've been talking about is in section 3.2 of the standard:

    For classes, it's a little murkier:

     
  25. jimmyb

    jimmyb 2[H]4U

    Messages:
    3,172
    Joined:
    May 24, 2006
    I usually put the declaration in the header,and then do a separate implementation.

    In the future I'll just use extern, as it seems a lot easier.
     
  26. CefiroZ

    CefiroZ Limp Gawd

    Messages:
    218
    Joined:
    Jan 17, 2004
    I second that!

    Well deserved Mike. People like you are the reason HardForum is such a great place and refreshing change of pace from the rest of the internet ;)

    My school's library has that in so I'll pick it up next time I'm on main campus (who knows when that'll be?) . If it isn't for me I won't hold it against you ;)

    And, for everyone else, thanks for the posts, this thread ended up being quite a good discussion!
     
  27. mikeblas

    mikeblas [H]ard|DCer of the Month - May 2006

    Messages:
    12,858
    Joined:
    Jun 26, 2004
    Another book I noticed at the store yesterday is C++ Gotchas by Dewhurst (ISBN 0321125185). It still doesn't mention this issue specifically, but does cover lots of "deeper language issues".