Skip to main content
GameDev.net gamedev.net
🔒 Locked

Are globals really THAT bad?

Started by nsto119 Feb 23, 2008 at 9:07 AM 28 replies 4.5k views
Original Post
nsto119
nsto119
Most people around say that you should avoid globals like the plague... why? Every single large piece of software I've ever worked with has used globals extensively. Often, there IS data that you want to have global access to. Not to mention, it seems like just using a global variable is often much, much easier than trying to hide it but at the same time allow pieces of code that need to, access it. I dunno, it's just a thought I had a while ago.
Rydinare
Rydinare
Quote:
Original post by nsto119
Most people around say that you should avoid globals like the plague... why?

Every single large piece of software I've ever worked with has used globals extensively. Often, there IS data that you want to have global access to. Not to mention, it seems like just using a global variable is often much, much easier than trying to hide it but at the same time allow pieces of code that need to, access it.

I dunno, it's just a thought I had a while ago.


Generally, yes. It's that bad. The problem is that it's a near-sighted decision. I'm actually okay with globals on small utilities, say less than 1000 SLOC. Past that, they're generally a maintenance nightmare. The problem comes when you want to change the behavior in a module or refactor. Try taking any reasonably complicated piece of software, and determine that you want a different behavior for some "global" aspect (take logging as a simple example) -- ultimately you want to be able to make that change without actually changing the class that is taking advantage of that behavior. That's the open/closed principle.

A quick example:

class MyClass{...};// All over your cpp...void MyClass::oneMethod(){   ...   g_app.log("Here's some useful information");   ...}


Now decide, you want to change that from logging to a file to logging to the debug window or logging to the screen or logging to a remote terminal. Ok, you can get around it by changing your app class and recompiling the entire application (doesn't scale very well, does it?). Even that, you could manage. Now, let's say that MyClass is controlled by MyComponent, and MyComponent decides that MyClass needs to log differently than the rest of the app, for reason xyz. Oops, you're doomed. Now you're manually changing MyClass when it already worked! Why? Because of use of a global. To avoid global madness, you should've had a logging interface that MyClass could take advantage of, without knowing anything about the implementation. Example of flexibility without global restrictions:

class ILog{public:   virtual void log(const std::string& p_text) = 0;};class MyClass{public:   MyClass(ILog& p_log);...private:   ILog& log_;};// in MyClass.cpp:void MyClass::oneMethod(){   ...   log_.log("Here's some useful information"); // No known implementation details of how it's logging = a win   ...}


Now, if MyComponent wants MyClass to log differently, it simply constructs it differently:

MyClass myClass(screenLogger_);


or

MyClass myClass(remoteLogger_);


or

MyClass myClass(debugWindowLogger_);


Globals add unnecessary restrictions to your application. At worst, use them with extreme care and eye them with much suspicion. At best, avoid them completely.
Casino
Casino
Are they that bad? I do not have the expertise that others have, but i've found that "Global Variables" are ones that take up memory that does not get released until the program itself ends. This infact can be Undesirable on a computer because that means you are allocating resource for something at a point and time when that resource does NOT need to be used, but more importantly, every single aspect of your program had access to the Global Variable and thus, could easily change it at any given time while controle of it was assumed. This could lead to some unexpected errors.
From what i've seen, things such as pointers, allow a few things that advantage over Globals.
For starters:
Pointers allow one to allocate memory when neccessary and with proper clean up, you can free that memory at any given time. This makes resource managing much easier. Sometimes how much resource your computer has will effect the programs, and sometimes, the computer's performance. With todays power houses this may not become very apparent with smaller programs, but in larger more complex programs, it shows.
Also, i've noticed that pointers all one to allow the rest of the program to access information under a more strictly controled enviorment. A pointer can allow a function access to the info, allow it to use the information as it needs, without worry that when the function is done, the information will have been altered.
I might be a bit misguided on this one, but I think that's pretty much some of the big issus globals tend to make. I'd say they are all "That Bad", because I myself still use one or two of them, but when there is a better alternative option, why bother?
Just Plain Looney-----------------
Intrigue7
Intrigue7
Well, global variables are not "evil" but they are not "perfect" either. Having global can be good for a FEW reasons like performance. I mostly see global variables in the procedural world and I see it a lot less in the Object-Oriented languages. Before putting a variable Global, you should always ask yourself... Do I really need to put this global... Where will I use this variable... if you're not sure or you will only need it in 1-2 functions, then do not put it global and add it as a parameter.. Remember that a global variable stays in the memory for as long as your program lives.. You will tell me that these days.. we have more than enough memory on every pc but... that's the lazy answer of saying: "Why should I code properly if I do not need to?". That's a way of seeing it... But not the best way to improve your programming skills.

There are 10 kinds of people in this world....Those who understand binary and those who don't.
ToohrVyk
ToohrVyk
Global variables (or other identifiers) are not really a problem, except for some language-specific issues such as order of initialization. The problem stems from global variables used to implement global mutable states, because the existence of global mutable states hurts code reuse.

(As a side note, it's important to know that code reuse is not restricted to reusing a piece of code from another project: it's perfectly possible, and even interesting efficiency-wise, to reuse code within the same project).


In essence, reusing code means using code to perform the same operation on two distinct objects in two distinct places in your code. A typical example is a sort algorithm, which can be used to sort the high-scores, to sort the projected intervals in the collision detector, or to sort the polygons in front-to-back rendering.

The existence of global mutable state inside a function makes that function very difficult to reuse, because its signature becomes more complex: to interact with the function correctly, one does not only need to pay attention to its arguments and return value, it also needs to make sure that the global state is as intended before operating. Because of this, the more a function relies on global state, the harder it is to reuse, and so its functionality will probably have to be rewritten again if a similar problem arises and needs to be solved.
Yann L
Yann L
I personally find this general global-o-phobia a little exagerrated at times. Sure, a global ridden program is a pain to maintain. But ironically, a few well selected central globals can make code much easier to maintain, and also make it faster.

In larger projects, we usually have a central class that holds instances to the main subsystems of the application (eg. GUI control, plugin management, renderer, configuration, error handling, etc). Most of these instances can be seen as global system services, that are (and should be) available to everybody. Now, passing around pointers to these objects is just dumb, and a maintenance nightmare on its own.

We habe instead opted to create a single global with the instance of the applications subsystem tree. That instance is explicitely created on startup and destroyed at shutdown. During operation, everybody can access the services by using the global pointer. This has proven to work very well, to be highly scalable, and easy to maintain.
Antheus
Antheus
Globals with state cause problems with concurrent programming. A single such global can bring a heavily concurrent application down to performance of single-threaded.

Stateless or immutable globals have little to no impact, aside from situations where resource replication is desirable.

As an practical example - Zlib. Zlib's state is considerably large ~256k. While it would make sense to make it global, average use of state altering functions will tax CPU and make sharing undesirable. Replicating the state per each thread requires n x 256k memory, but it can be allocated on stack (if desirable), and will likely be more local. Aside from possibly better cache locality, it also doesn't present any contention problems.

Globals have many subtle or obvious pitfalls, and generally don't play well with mixed models. Fully single-threaded, globals-based application will be reasonably easy to maintain, reasonably simpler to develop, and very straight-forward. Mixing state-based idioms however will introduce two opposing models which will clash with each other.

Similarily, no-shared-state applications, perhaps fully functional approach has many benefits, which are often smeared by introduction of globals.

Programming models are changing. Concurrency is the new way to go. Large, distributed and loosely-coupled applications.

Globals stand in way of all that. There are hybrid attempts, such as STM. But even that one has huge problems with heavily contested resources.

Even today, people use goto. C++ applications are written with use of malloc. Char * is teh fastest. And so on.

Cultural impact determines what is right or wrong, technical aspects never mattered (Hungarian, anyone?). It's interesting that business world, once in absolute hype over Singletons started avoiding them like plague. And since they represent some 60% of global IT market, are they really right, or simply following another hype? Or perhaps their programming models simply could no longer accommodate them, as the systems scaled into mostly clustered and distributed computing?
Rydinare
Rydinare
Quote:
Original post by Yann L
I personally find this general global-o-phobia a little exagerrated at times. Sure, a global ridden program is a pain to maintain. But ironically, a few well selected central globals can make code much easier to maintain, and also make it faster.

In larger projects, we usually have a central class that holds instances to the main subsystems of the application (eg. GUI control, plugin management, renderer, configuration, error handling, etc). Most of these instances can be seen as global system services, that are (and should be) available to everybody. Now, passing around pointers to these objects is just dumb, and a maintenance nightmare on its own.

We habe instead opted to create a single global with the instance of the applications subsystem tree. That instance is explicitely created on startup and destroyed at shutdown. During operation, everybody can access the services by using the global pointer. This has proven to work very well, to be highly scalable, and easy to maintain.


Take the above advice with the caveat, as long as the entire app requires the same behavior. The moment this assumption is not true, the whole model falls apart, and the proper way to refactor becomes unclear. Since you can't really know what future needs are (we usually have barely a sense of what current needs are), it can and often proves to be a bad decision when making things global.

Anyway, the above is my usual knocks against it. That being said, I can agree that, in certain instances, the dependency of having to pass around a "global" type of object such a logger everywhere can become a bit of annoyance. Is there a compromise that would work around both concerns?
Antheus
Antheus
Quote:
Original post by Rydinare

Anyway, the above is my usual knocks against it. That being said, I can agree that, in certain instances, the dependency of having to pass around a "global" type of object such a logger everywhere can become a bit of annoyance. Is there a compromise that would work around both concerns?


No. The whole point is that global is implicit reference resolved at compile-time. The "inconvenience" implies tight coupling of the application. Using a global hardcodes the reference. This saves passing the reference around, and, truth be told, can reduce memory use considerably, possibly reducing it by a factor of 2 or 3 (properties and property listeners are an example of this in C++).

But consider the following. The "only one instance" becomes problematic when you're trying to write a distributed application. Distributed may apply to multi-core, multi-process or network distributed.

This isn't an issue yet - but it's becoming one rapidly. Fortunately, current OSes are heavily single-threaded, making this a non-issue, since other problems are much bigger. But once you start getting involved with this, and if you care about performance, mutable globals with state become undesirable.
LordShade
LordShade
Like Yann L said. Globals are handy for access to common functionality. You can think of this as a Service for your application.

For people just starting out in game programming or programming in general, spending too much time worrying about certain design issues will slow your progress. Don't worry about these things too much. Write your code. Get stuff working no matter how "ugly" you think it is. The more software you write, ultimately the better your next design will be.

EDIT: Some of these example are pretty damn funny. Just write your code any way you see fit. People need to remember that most people here with these sorts of questions are not writing with a massive team. They're "Lone Codeman." Don't get dogged down by details like multi-threading and distributed environments until you're ready.

Happy coding.
squicklid
squicklid
I hate globals for the most part.

Globals cause spaghetti code - macrame of frail data. Pull one piece, and everything unravels.

I have seen it used extensively in a professional setting, and it is bad. Bad programming practice made people set globals before calling a subroutine, and then they check the globals after it returns. You should pass things as parameters. If there are too many values to pass, put them into a structure and then pass that. If a function needs to pass back a lot of data, have it take a pointer to a structure, and populate it.

There are very few times when you should be using globals. Real modular programming avoids globals like the plague because they are so hard to maintain.
Yann L
Yann L
Quote:
Original post by Rydinare
Take the above advice with the caveat, as long as the entire app requires the same behavior.

Define 'require the same behaviour'. A certain subsystem provides a certain known service with a certain, known, and well documented interface. Maintaining this framework is extremely important, especially with an application that relies heavily on a plugin architecture. There is no difference here between offering this service via a global request mechanism, or via handing around pointers or references to it.

Quote:
Original post by Rydinare
we usually have barely a sense of what current needs are

In that case, you have a case of bad project planning.
Rydinare
Rydinare
Quote:
Original post by Yann L
Quote:
Original post by Rydinare
Take the above advice with the caveat, as long as the entire app requires the same behavior.

Define 'require the same behaviour'. A certain subsystem provides a certain known service with a certain, known, and well documented interface. Maintaining this framework is extremely important, especially with an application that relies heavily on a plugin architecture. There is no difference here between offering this service via a global request mechanism, or via handing around pointers or references to it.


Well, again, take the simple example of logging. If the entire app logs to same place, no problem. If you need different behavior in different classes, components, etc..., you'd be modifying individual, otherwise reusable classes, to incorporate the flexibility. One of the benefits of object-oriented designs are that, of importance, is not just the fact that you have to make changes, but, actually, where changes are made.

Quote:
Original post by Yann L
Quote:
Original post by Rydinare
we usually have barely a sense of what current needs are

In that case, you have a case of bad project planning.


Actually, at my organization, that would be an accurate assessment. Moreso, I'll say that planning only goes so far. Even if you see the majority of the road ahead of you, you won't see it all at once, and you certainly won't see every road along your path. Certainly, many projects go through many iterations. Thus, you have to plan for flexibility.

One of the largest expenses to organizations and to individuals, for that matter, is throwaway code. I strive to avoid that.
frob
frob
Quote:
Some of these example are pretty damn funny. Just write your code any way you see fit. People need to remember that most people here with these sorts of questions are not writing with a massive team. They're "Lone Codeman." Don't get dogged down by details like multi-threading and distributed environments until you're ready.

Ditto.

Even when they ARE writing on a team, decisions about threading and environments will be made by your leads and the senior engineers who have enough experience to know the answers.

In those environments there are policies in place about globals, singletons, locks, and other easily abused bits of code. There are peer reviews, and mentors. And most importantly, there are co-workers who will razz you for weeks or months to encourage enforcement.


But for most people on the board, their goal is to write a game, not to architect an all-encompassing system that will survive decades of scrutiny. The most important task is to get something done.
Antheus
Antheus
Quote:
Original post by frob

But for most people on the board, their goal is to write a game, not to architect an all-encompassing system that will survive decades of scrutiny. The most important task is to get something done.


Then what should the answer to original question be?

No, globals are fine. Moving on....

And to reference the OP:
Quote:
Every single large piece of software I've ever worked with has used globals extensively


Should the conclusion then be that everything surrounding globals is just FUD?

Sure, why not.

The problem is, since this is coming from someone who appears to have some real world experience, not just a random hobbyst, what about 2 years from now?

My favorite counter-singleton/counter-global argument is log4cpp's static initialization order fiasco Search here for "initialization". I tried this about a year ago, and it failed in the same way, 2 years after the problem was discovered.

But then again, it's apache project, it has to be good, right? Professional programmers wouldn't make such a trivial mistake, right? Then again, it's probably just a fluke.

After noticing that problem, I deleted log4cpp without hesitation. It was completely useless for server application. That, and a severe memory leak the framework has.

Yes, many applications use globals. And since log4cpp is basically one-for-one port from Java, it has same use of singletons. And many locks. And many parts of synchronized code.

I don't know, what should the conclusion be. All the big guys do it, so it's fine? Perhaps it is. Perhaps log4cpp really sucks for hobbysts, who are much better off spending two weeks writing, debugging and fine-tunning their own version.

What should the answer be then? Yes, globals are fine under certain circumstances, and they work well. Should everyone now just stick fingers into their ears as not to hear anything else?

Go read the beginner forums - "why am I getting linker errors"? "How do I split my application into two files?" "How do I solve the mutual dependency problem?"

These are just C++-related globals problems, and they fall under basic understanding of C++ OO. And since most are taught the use of globals, most never move on from that, and never even try to find an alternative. Let's not even go into C-related globals problems and name clashes. And BNCED_APP_UTIL_24_EXT_O253A_DEVNAME_PP_foo() functions there. Or the double-checked-locking JVM fiasco, which exposed that fundamental threading paradigm was broken up until Java 1.5. Or the problem with singleton-based enums and data corruption it caused when serialization created two instances and incremented the static global counter.

It's not a one-size-fits-all solution. Shouldn't one at least try to understand the alternatives?

Quote:
Even when they ARE writing on a team, decisions about threading and environments will be made by your leads and the senior engineers who have enough experience to know the answers.


So your advice is that everyone just settle into being a code monkey? Shouldn't one's aspiration be in trying to improve themselves to progress into senior positions?

Shouldn't one aspire to be the one who knows all the answers?
Kuro
Kuro
Some of the most frustrating experiences I've had as a programmer had to do with global variables being used in sloppy ways.

For example, the engine I'm working with has different global modes which control how it operates. One time I spent a good chunk of a day hunting down a bug, only to find that the problem was happening in a completely different place where I thought it was (some global was being set incorrectly).

I've also seen workarounds like this:

float oldVisibility = gVisibilityOverride;
gVisibilityOverride = alpha;
renderObject(); // uses gVisibilityOverride
gVisibilityOverride = oldVisibility;

If globals are used for system-wide resources like as Yann described I think that's okay but in general I'd be careful with them. I think about variables like building a computer... If you keep your wires short and organized, it's easy to see what's connected to what, but if you have 50 wires going all over the place then it's a nightmare.
stonemetal
stonemetal
Globals are bad because they show a lack of thought and design. You couldn't do it right so you just slapped it into the global namespace and moved on. Now is it possible for them to be used in a well thought out design? sure. Do I see them in my day to day professional code? Not by my choice but, yes. The for beginners board sees a lot of bashing on globals because people are trying to get the noobs to think when they code, not just bang on the keyboard.
Darragh
Darragh
Quote:
Original post by nsto119
Most people around say that you should avoid globals like the plague... why?


Generally, yes. It's a bit like the 'goto' statement in C/C++; for the most part it is not a good idea to use it but there are certain situations where it can actually be helpful and reduce the complexity of your code. You'll have to be the judge of that choose when and how you will use it; but most of the time you'll find you won't need it.

If you do decide that you need globals then bear in mind some of the disadvantages that they bring:

1 - From a performance point of view they are bad. Locals are generally faster to access because they are stored on the stack. The stack is generally pretty well cached whereas a global stored in some random memory location generally won't be.

2 - Memory wise of course, globals are always in existence so they are bad in that respect. Not a huge disadvantage really though, especially if your globals are just pointers to objects rather than objects themselves.

3 - When it comes to debugging they are a nightmare to work with. This is because you cannot keep track of when they are written to or by what.

4 - They can actually increase code complexity if used too much. It is always a good idea to limit the scope of each variable and keep it restricted to the smallest area of code possible. It makes the program harder to understand if you have lots and lots of variables that are valid at any particular point in the code.

5 - They are not thread safe; which is a huge disadvantage these days considering the direction in which hardware is moving! This one has actually caused me considerable grief in the past, and personally I think it's one of the biggest reasons to stay away from them.

Of course the main advantage of globals is that they are available everywhere and reduce the need for writing accessors / modifier functions, so they can save a some time and code complexity in that regard. Generally though I think the disadvantages outweigh the advantages; make the choice that is right for the situation you are in.

Hope this helps,
Darragh
taby
taby
Yes, global variables are bad. They are a cheap and lazy way to avoid having to pass extra parameters around from function to function.

I won't name names, but a very popular open source secure FTP package uses global variables extensively. The author attempted to "clean up" properly when the functions return, but the attempt utterly failed (did they not test this stuff at all?). If you want to integrate their source code into your own, the only way I could do it successfully was to move it into its own DLL, then manually link into it at runtime, run the function one needs, then unlink the DLL. This has to be done every time the function is called. VERY DISGUSTING. If I had known it would have been this way, it probably would have taken me less time to write my own package for all of the time I wasted trying to fix the broken code.

Don't use global variables.

I'm always leary of suggesting the use of singleton classes in C++, but at least they are one huge step better than global variables.
Rydinare
Rydinare
Quote:
Original post by taby
Yes, global variables are bad. They are a cheap and lazy way to avoid having to pass extra parameters around from function to function.

I won't name names, but a very popular open source secure FTP package uses global variables extensively. The author attempted to "clean up" properly when the functions return, but the attempt utterly failed (did they not test this stuff at all?). If you want to integrate their source code into your own, the only way I could do it successfully was to move it into its own DLL, then manually link into it at runtime, run the function one needs, then unlink the DLL. This has to be done every time the function is called. VERY DISGUSTING. If I had known it would have been this way, it probably would have taken me less time to write my own package for all of the time I wasted trying to fix the broken code.

Don't use global variables.

I'm always leary of suggesting the use of singleton classes in C++, but at least they are one huge step better than global variables.


Your post was going well, but singletons aren't really a huge step better than globals. Singletons barely offer an advantage over regular globals. The better solution is to just not use globals.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.