global variables

Started by
13 comments, last by Antheus 15 years, 9 months ago
Hey I have about a million global variables. My game program became huge and then I split it into about 20 separate .cpp and .h files so it's managable again. To keep using the global variables I had to say [souce] extern int inum; [/source] and so on for every global variable I need in all my .h files. Anyway it's working fine but everyone says if you use global variables then you'll die and go to hell so what exactly is the other option? I could get rid of all the global variables and declare them in main() and then pass them to all the functions that need them but some of those functions would need like 25 variables passed to them. I'm not doing that. As far as I can tell global variables are the best option unless there's something I don't know. (That's happened before.)
Advertisement
Quote:
pass them to all the functions that need them but some of those functions would need like 25 variables passed to them. I'm not doing that. As far as I can tell global variables are the best option unless there's something I don't know. (That's happened before.)

This is commonly considered to be the solution, however it's only halfway there. Yes, you need to pass the data to every place that needs it -- but the larger problem, the problem you have got yourself into using globals (and part of why globals are thus considered bad) is that you have too many dependencies on too many objects, so the result is that in order to pass everything needed by the various subsystems to those subsystems... you have to pass everything to everywhere.

The solution is to reduce the number of dependencies. This is a major change and will involve extensive iterative refactoring of both your design and implementation, such that it may not be something you are willing to do at this point with this project (although it is a good idea, and good practice). If that's the case make sure you remember this for the next project, so that you don't dig yourself into the hole you're already in.

If you provide specific examples of your systems and how they are designed and how they talk to eachother, we may be able to suggest alternative designs that can help reduce dependencies.

If you choose to perform the refactoring, make sure you do it iteratively, a small change at a time. Demote one global at a time into main() (or whatever) and pass it down the chain until everything that needs it has it. Make sure everything compiles, runs, and passes your tests. Repeat until all globals have been demoted. At that point you can consider how you can revise the design of these subsystems to remove the dependencies on that data, and apply a similar refactoring process.

It is possible to do this cleanly and elegantly, although you may not be able to see that from here. All of the code I've written in the last few years, in both a personal and professional context, uses no globals and rarely passes data more than two or three 'levels of isolation' deep.
If you need to pass a couple of variables, declare them in a struct and pass the struct.

Also, 25 arguments? Seems like you'd need to split those functions or atleast refactor your code.

I have no global variables at all, I would do everything to avoid them.
[size="2"]SignatureShuffle: [size="2"]Random signature images on fora
Here's all the global variables:
Gizmo hero;Gizmo *dummygiz;GLuint		base;  // Font Display List  (nehe's lesson32)int irow, icol;int irow2, icol2;int fx,fy;int fxstore, fystore;int istate;ifstream infile;   //the currently loaded mapfstream savefile;  //player's saved game, stays open all the time?ofstream outfile;  //opens for only a brief moment to create a save fileint ix, ij;int itrash;//float flayer6;string rgcfilename;    //i forgot if these are re-usable or if they are off limitsstring rgcloader;      //i think just used at beginning menu time so re-usable after that?string rgcplayername;string rgclayer1;      //re-usable in most situations, it saves something during links so watch outchar ctrash;Iceint icetrash;Iceint iceblock;       //value of block you're moving toIceint icex;Iceint icey;Iceint icetype;Uint32 elticko;GLUquadricObj* quadratic;GLdouble dval;int itrick;//int itrick2;  //used for animationGizBox monsterbox;PicBox monsterstore;int ilinkspeed;float rot;ImageVault picbox;SDL_Event event;int done;Uint8 *keys;WeatherBox nature;AudioBox boombox;Icematrix icemap;//Mix_Chunk *chunktest;//delete ijunkint ijunk;

Some of those I could get rid of pretty easily. For instance ix and ij are just iterators. Obviously I could have functions declare their own iterators. When I started this project I didn't know ANYTHING. For some reason I jumped right in and started programming a massive Zelda-like game editor and engine without doing anything small first.

So back then I was thinking I would just make those iterators global and then all the functions that use iterators wouldn't need the extra memory for their own iterators. Technically I was correct, I believe. But in the days of 2 gigabytes of RAM I'm starting to think it's a silly thing to worry about.

This is the .h file that contains my function for drawing the game:
extern void drawblocks();extern int istate;//extern GLfloat rgfmap[];extern string rgcloader;extern int irow2;extern int icol2;extern GLUquadricObj* quadratic;extern GLdouble dval;extern WeatherBox nature;extern Iceint iceblock;extern Uint32 elticko;extern float rot;extern GLuint imgmap[];//delete ijunkextern int ijunk;void draw();

istate - I need that because it draws differently depending on what state the program is in. A few functions need istate. The function that deals with key presses, for instance.

rgcloader - I need that because the characters the user is inputting are stored in that and the draw function is displaying them

I need probably over half of those variables. I don't see how I can make my draw function not depend on them.

Also there are a couple other global variables that my draw() function uses but for some reason the compiler doesn't care that I didn't declare them with extern above. I have no idea why.
Quote:Original post by icecubeflower
Some of those I could get rid of pretty easily.


So, do the easy ones, and then we can talk.

Quote:For instance ix and ij are just iterators.


No, they aren't. They are loop counters.

Quote:Obviously I could have functions declare their own iterators.... So back then I was thinking I would just make those iterators global and then all the functions that use iterators wouldn't need the extra memory for their own iterators. Technically I was correct, I believe.


No. When control flow reaches the point where the variable is declared (this is a simplification; optimizing compilers can rearrange stuff), the memory is allocated for it on the stack. When that variable's scope is exited (e.g. at the end of the function, or within the loop it was declared within, if applicable), the memory is deallocated from the stack. The variable takes the same amount of memory whether it is in on the stack or (as the global) in static storage. So there is nothing to save here, except in cases where it would be incorrect to do so (e.g. for recursive algorithms, where the global would mean each recursive call re-uses the same variable data, and everything will get messed up).

Quote:istate - I need that because it draws differently depending on what state the program is in. A few functions need istate. The function that deals with key presses, for instance.


So, you declare it in main, and pass it around to the functions that need it.

Quote:rgcloader - I need that because the characters the user is inputting are stored in that and the draw function is displaying them


Similarly.

Quote:Also there are a couple other global variables that my draw() function uses but for some reason the compiler doesn't care that I didn't declare them with extern above. I have no idea why.


Because they aren't used in other translation units.
Oh, then yeah, they are loop counters. Sorry, my vocabulary is a work in progress.

The biggest problem I had was that I couldn't plan ahead, I didn't know anything. It was like I had to build a skyscraper without knowing anything about structural engineering. There were no blueprints, I just grabbed a hammer and some nails and went to town.

See that stream, infile? I open a file that contains information about the enemies and walls and what png files to use for the map. That thing just stays open all the time and all my functions use it. If I'm going to make it stop being global then I'll be passing it to many functions. Would it be wiser to close the file whenever I'm not using it and pass a string with the filename instead and just have the functions reopen it? Or is it fine to just open a file and leave it open?
Maybe this gives you a clue on how to not use globals:

Global:
[source code="cpp"]int i;int main(){    for (i = 0; i < 10; ++i)    {        // loop    }}


For local:
[source code="cpp"]int main(){    for (int i = 0; i < 10; ++i)    {        // loop    }}


Function local:
[source code="cpp"]int main(){    int i;    for (i = 0; i < 10; ++i)    {        // loop 1    }    for (i = 0; i < 10; ++i)    {        // loop 2    }}


Passing it to functions:
[source code="cpp"]void function(int number){    std::cout << number; // 10}int main(){    int i;    for (i = 0; i < 10; ++i)    {        // loop 1    }    function(i);}


I hope this helps you a bit...
[size="2"]SignatureShuffle: [size="2"]Random signature images on fora
Quote:Original post by icecubeflower
Oh, then yeah, they are loop counters. Sorry, my vocabulary is a work in progress.

The biggest problem I had was that I couldn't plan ahead, I didn't know anything. It was like I had to build a skyscraper without knowing anything about structural engineering. There were no blueprints, I just grabbed a hammer and some nails and went to town.

See that stream, infile? I open a file that contains information about the enemies and walls and what png files to use for the map. That thing just stays open all the time and all my functions use it. If I'm going to make it stop being global then I'll be passing it to many functions. Would it be wiser to close the file whenever I'm not using it and pass a string with the filename instead and just have the functions reopen it? Or is it fine to just open a file and leave it open?


Open it, copy it into a buffer, close it, pass the pointer of the buffer to functions or interpret the data into arrays or variables...

[size="2"]SignatureShuffle: [size="2"]Random signature images on fora
Nah, I already knew that. I'm talking about variables like istate and rgcloader that functions need. I'm gonna backup my programs and try getting rid of my globals one by one. It just seems to me that some functions will need a lot of variables passed to them.
Hey I was responding to the post at 3:42, not your last one.

This topic is closed to new replies.

Advertisement