2

I keep hearing that global variables should never be used, but I have a tendency to dismiss "never" rules as hot-headed. Are there really no exceptions?
For instance, I am currently writing a small game in c++ with SDL. It seems to me to make a lot of sense to have a global variable with a pointer to the screen buffer, because all the different class that represent the different type of things in the game will need to blit to it, and there is only one screen buffer.

Please tell me if I am right that there are exceptions, or if not then:

  • Why not, or what is so bad about them that they should be avoided at all costs (please explain a little bit)
  • How can this be achieved, preferably without having to pass it to every constructer to be stored internally until needed, or to every call to a paint() request.

(I would assume that this question had been asked on SO before, however couldn't find what I need (an explanation and workaround) when searching. If someone could just post a link to a previous question, that could be great)

Baruch
  • 20,590
  • 28
  • 126
  • 201
  • 1
    I just wanted to comment that I greatly share your point of view when it comes to the term "never use" in coding. I just love finding a practical use for everything! Good luck! P.S. I am making a temporary exception for GOTO , that's just scary – Proclyon Nov 04 '10 at 22:49
  • Awww, you know you love GOTO, just because you call it BREAK or CONTINUE shouldn't matter. – Greg Domjan Nov 05 '10 at 00:20
  • 1
    The way to interpret "never do X" rules is not to dismiss them. They're generally there for a good reason. Instead *understand* them. Why are people giving this advice? What are the alternatives, and why are they preferred? And when you know that, you also know when to "forget" the rule. The rule to avoid goto is fine, and makes a lot of sense. But it's a general rule, it doesn't know about the special circumstances you're in. And @Greg, there's a major difference between `break`/`continue` and `goto` as far as Dijkstra's objections to gotos are concerned – jalf Nov 05 '10 at 01:38
  • If you are writing a 5 line program them who cares. If you are writing some huge mega program that needs to be maintained then follow the generally accepted advice until you understand why (and then you can make judgment calls). – Martin York Nov 05 '10 at 09:44
  • @jalf: Perhaps you could link to those objections, my original comment was not intended in a serious manner, more humour on people singling out little old goto. – Greg Domjan Nov 05 '10 at 19:15
  • @Greg: http://www.u.arizona.edu/~rubinson/copyright_violations/Go_To_Considered_Harmful.html :) And sorry if I misinterpreted you, but I've run into a surprising number of people who try to defend gotos by saying "Yeah but language feature `[x]` is just a disguised global too" (where `[x]` might be a function call, a loop, an `if`, `break`, `continue` or `throw`. Many language features implement some kind of flow control like a `goto` does, and many language features compile down to a `goto` at machine level, but that doesn't mean they're equivalent to `goto`s. – jalf Nov 06 '10 at 14:38
  • There are good reasons for singling out "little old goto" and not all the other forms of flow control. (Most of those were added to *replace* goto) – jalf Nov 06 '10 at 14:39

11 Answers11

8

We tell students never to use global variables because it encourages better programming methods. It's the same reason we tell them not to use a goto statement. Once you're an accomplished programmer then you can break the rules because you should know when it's appropriate.

JOTN
  • 6,120
  • 2
  • 26
  • 31
  • I would like to know more details of how not using a global variables leads to better programming methods? How do you tell students to define constant variables? – Leonid Nov 04 '10 at 22:20
  • That's actually a really good question. Is there really such a thing as a "constant" in C++? (By "constant" I mean something that could live in ROM, or get compiled inline and have its storage eliminated, and the program will still be correct.) Sure, there's a keyword `const` that makes something *seem* constant... until the programmer `const_cast<>` it away and it's suddenly just another variable. The only "constant" things I can think of in C++ are constant expressions (usually made into `#define` symbols) or `enum` symbols. – Mike DeSimone Nov 04 '10 at 22:36
  • 1
    Not using global variables creates modular code. When you call a function, you know it's only operating on what you hand it and there's no unexpected side effects. That makes it much easier to come back later or have someone else work on it. Games are performance critical, so it may be worth sacrificing maintainability for performance. – JOTN Nov 04 '10 at 22:39
  • Constant is *constant* for as long as it's not const_casted<>. It is awkward and obvious in the code, however sometimes can safe your life. Const is a hint to complier and protects coder from doing stupid modifications. If `const_cast<>` is used there is certainly a good reason for that. – Leonid Nov 04 '10 at 22:46
  • 1
    @Mike DeSimone: a `const int` in C++ is a compile-time constant, and can have its storage eliminated if it (a) has internal linkage, and (b) the compiler detects that its address is never taken. In general, `const` objects (i.e., those where the object itself is defined as const, not just some const pointer to it exists), are truly const, and could validly be stored in ROM if the compiler knows how to get them there (e.g. first initialize them, then mark the page they occupy as read-only). You *could* cast away const and try to modify, but it's undefined behaviour: a.k.a your fault, not mine. – Steve Jessop Nov 04 '10 at 22:48
  • @Mike: actually, hold that generality. I can't remember what happens with defined-const objects that have mutable members that are modified in `const` member functions. Probably they couldn't be stored in ROM and the program still be correct, so my generalization needs an extra caveat: no mutables. Certainly, though, stuff that can be used in constant expressions (5.19) is compile-time constant. The fact that an invalid program might appear to modify it is neither here nor there :-) – Steve Jessop Nov 04 '10 at 22:52
  • But can you *guarantee* that a, say, `const int` that you defined in a header will not have external linkage? Say, with `static`? Or does that break something else? I ask because there is no way for a library writer to be sure that a library user will not take the address of said variable, and if they do this more than once the link will fail with a multiple-defined-symbol error, which might be hard to track down. Also, can this be extended to any POD types? – Mike DeSimone Nov 04 '10 at 23:01
  • @Mike: At namespace scope, `static` gives any object (POD or not) internal linkage, and a `const` object which isn't given external linkage has internal linkage. There's some faff to do with multiple declarations, in 3.5, but that's the executive summary. The library writer doesn't need to care whether a user takes the address - even if they do, they can only "modify" the object by invoking UB. Modify deliberately placed in inverted commas, because what that really does is place the program into an undefined state which may, or may not, act as though the object is modified. – Steve Jessop Nov 04 '10 at 23:14
  • ... rather like a string literal, or the machine code for a given function. Yes, your program may be able to do undefined things which modify it on certain platforms (respectively: cast to non-const; perform a privilege escalation attack on the kernel to gain write access to memory pages marked executable). But as far as the language is concerned that all happens on the far side of the event horizon of UB, and the library writer is entitled to assume that the client doesn't invoke UB. – Steve Jessop Nov 04 '10 at 23:18
  • @Leonid: `const_cast` cannot be used to write to an object that was made `const` at the point of definition. It can only be used to undo cv-qualifiers from a pointer (or reference) to a non-`const` instance. – Ben Voigt Nov 05 '10 at 02:14
7

Like any other design decision, using global variables has a cost. It saves you having to pass variables unnecessarily, and allows you to share state among running functions. But it also has the potential to make your code hard to follow, and to reuse.

Some applications, like embedded systems, use global variables regularly. For them, the added speed of not having to pass the variable or even a pointer into the activation record, and the simplicity, makes it a good decision [arguably]. But their code suffers for it; it is often hard to follow execution and developing systems with increasing complexity becomes more and more difficult.

In a large system, consisting of heterogeneous components, using globals may become a nightmare to maintain. At some point you may need a different screen buffer with different properties, or the screen buffer may not be available until it's initialized meaning you'll have to wrap every call to it with a check if it's null, or you'll need to write multithreaded code, and the global will require a lock.

In short, you are free to use global vars while your application is small enough to manage. When it starts to grow, they will become a liability, and will either require refactoring to remove, or will cripple the programs growth (in terms of capability or stability). The admonition not to use them stems from years of hard-learned lessons, not programmer "hot-headedness".

Zack Bloom
  • 8,309
  • 2
  • 20
  • 27
  • I'm working on the code for an embedded ARM system right now. I'm working from vendor-provided example code and "libraries" that, while not using globals, treat the I/O registers as de-facto globals. There's one (7000 line) master header file with all the register definitions, and one (800 line) "board.h" file with all the board-level settings, and everything uses these headers. This makes it hard to tell, without reading *all* the code, what depends on what. – Mike DeSimone Nov 07 '10 at 21:07
  • Hence the "arguably" caveat. Many embedded developers believe that it is the best course. I don't have enough experience in the field to have a real opinion, so I expressed theirs. – Zack Bloom Nov 08 '10 at 03:58
7

Of course there are exceptions. I personally can't think of a single situation where a goto is the right solution (or where a singleton is the right solution), but global variables occasionally have their uses. But... you haven't found a valid excuse.

Most objects in your game do not, repeat, not need to access the screen buffer. That is the responsibility of the renderer and no one else. You don't want your logger, input manager, AI or anyone else putting random garbage on the screen.

And that is why people say "don't use globals". It's not because globals are some kind of ultimate evil, but because if we don't say this, people fall into the trap you're in, of "yeah but that rule doesn't apply to me, right? I need everything to have access to X". No, you need to learn to structure your program.

More common exceptions are for state-less or static objects, like a logger, or perhaps your app's configuration: things that are either read-only or write-only, and which truly needs to be accessible from everywhere. Every line of code may potentially need to write a log message. So a logger is a fair candidate for making global. But 99% of your code should not even need to know that a screen buffer exists.

The problem with globals is, in a nutshell, that they violate encapsulation: Code that depends on a global is less reusable. I can take the exact same class you're using, put it in my app, and it'll break. Because I don't have the same network of global objects that it depends on.

It also makes the code harder to reason about. What value will a function f(x) return? It obviously depends on what x is. But if I pass the same x twice, will I get the same result? If it uses a lot of globals, then probably not. Then it becomes really difficult to just figure out what it's going to return, and also what else it is going to do. Is it going to set some global variable that's going to affect other, seemingly unrelated, functions?

How can this be achieved, preferably without having to pass it to every constructor to be stored internally until needed

You make it sound like that's a bad thing. If an object needs to know about the screen buffer, then you should give it the screen buffer. Either in the constructor, or in a later call. (And it has a nice bonus: it alerts you if your design is sloppy. If you have 500 classes that need to use the screen buffer, then you have to pass it to 500 constructors. That's painful, and so it's a wake-up call: I am doing something wrong. That many object shouldn't need to know about the screen buffer. How can I fix this?`)

As a more obvious example, say I want to calculate the cosine of 1.42, so I pass 1.42 to the function: cos(1.42)

That's how we usually do it, with no globals. Of course, we could instead say "yeah but everyone needs to be able to set the argument to cos, I'd better make it global". Then it'd look like this:

gVal = 1.42;
cos();

I don't know about you, but I think the first version was more readable.

jalf
  • 243,077
  • 51
  • 345
  • 550
  • 1
    A good example of a valid `goto` is breaking out of several loops. More on the topic is in Knuth's article: http://pplab.snu.ac.kr/courses/adv_pl05/papers/p261-knuth.pdf – Vlad Dec 16 '10 at 09:57
3

What if you want to update your engine to support dual screen? Multiple displays are becoming more and more common all the time. Or what if you want to introduce threading? Bang. How about if you want to support more than one rendering subsystem? Whoopsie. I want to pack my code as a library for other people or myself to re-use? Crap.

Another problem is that the order of global init between source files is undefined, making it tricky to maintain more than a couple.

Ultimately, you should have one and only one object that can work with the screen buffer - the rendering object. Thus, the screen buffer pointer should be part of that object.

I agree with you from a fundamental point of view - "never" is inaccurate. Every function call you make is calling a global variable - the address of that function. This is especially true for imported functions like OS functions. There are other things that you simply cannot unglobal, even if you wanted to - like the heap. However, this is most assuredly not the right place to use a global.

The biggest problem with globals is that if you later decide that a global wasn't the right thing to do for any reason (and there are many reasons), then they're absolutely hell to factor out of an existing progam. The simple fact is that using a global is just not thinking. I can't be bothered to design an actual rendering subsystem and object, so I'm just gonna chuck this stuff in a global. It's easy, it's simple, and not doing this was the biggest revolution in software programming, ever, and for good reason.

Make a rendering class. Put the pointer in there. Use a member function. Problem solved.

Edit: I re-read your OP. The problem here is that you've split your responsibilities. Each class (bitmap, text, whatever) should NOT render itself. It should just hold the data that the master rendering object needs to render it. It's a Bitmap's job to represent a bitmap - not to render a bitmap.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • Thanks for the advice on my specific project. I have never made a game before and am sort of learning on the go (when I said simple game, I meant simple) – Baruch Nov 05 '10 at 06:32
2
  • Global variables can change in unexpected ways, which is usually not what you want. The state of the application will become complex and unmaintainable. Very easy to make something wrong. Especially if someone else is changing your code;

  • Singleton might be a better idea. That would at least give you some encapsulation in case you need to make extensions in the future.

  • One reason not to use global variables is a problem with namespaces (i.e. accidentally using the same name twice);

  • We do often use global (to namespace) constants at work which is considered normal, as they don't change (in unexpected ways) and it is very convenient to have them available in multiple files.

Leonid
  • 22,360
  • 25
  • 67
  • 91
  • +1 This is why I don't like to use globals (*or* shared/mutable states): **[with globals] the state of the application will [often] become complex and unmaintainable.**. I have a simple mind, and the simpler the code, the less I mess up :-) –  Nov 05 '10 at 05:08
2

If the screen buffer is shared between lots of different pieces of code, then you have two options:

1) Pass it around all over the place. This is inconvenient, because every piece of code that uses the screen buffer, even indirectly, needs to be laboriously indicated as such by the fact that this object is passed through the call stack.

2) Use a global. If you do this, then for all you know any function at all in your entire program might use the screen buffer, just by grabbing it from the global[*]. So if you need to reason about the state of the screen buffer, then you need to include the entire program in your reasoning. If only there was some way to indicate which functions modify the screen buffer, and which cannot possibly ever do so. Oh, hang on a second...

This is even aside from the benefits of dependency injection - when testing, and in future iterations of your program, it might be useful for the caller of some function that blits, to be able to say where it should blit to, not necessarily the screen.

The same issues apply as much to singletons as they do to other modifiable globals.

You could perhaps even make a case that it should cost you something to add yet another piece of code that modifies the screen buffer, because you should try to write systems which are loosely coupled, and doing so will naturally result in fairly few pieces of code that need to know anything at all about the screen in order to do their job (even if they know that they're manipulating images, they needn't necessarily care whether those images are in the screen buffer, or some back buffer, or some completely unrelated buffer that's nothing to do with the screen). I'm not actually in favour of making extra work just to punish myself into writing better code, but it's certainly true that globals make it quite easy to add yet another inappropriate wad of coupling to my app.

[*] Well, you may be able to narrow it down on the basis that only TUs that include the relevant header file will have the declaration. There's nothing technically to stop them copy-and-pasting it, but in a code base that's at all well regulated, they won't.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • As to that last point, I have a general function that blits. Then for every item on the screen, I have a class representing it each holds state like coordinates and direction and speed for movement etc. I call the draw() (or display, or show, or whatever) on the item, and that is supposed to call the blitting function with the screen buffer as an argument – Baruch Nov 04 '10 at 22:37
  • There are more than two options. – Clifford Nov 04 '10 at 22:40
  • @baruch: so what stops you from either (a) passing the screen buffer to `draw()` as well, or (b) passing the screen buffer to the class(es) representing the items as a constructor parameter? That is, either "a function to draw it *to the specified buffer* ", or "a class representing an item, *located on a specified buffer* ". – Steve Jessop Nov 04 '10 at 22:40
  • @Clifford: feel free to suggest them. I meant "accepting that the screen buffer is used in lots of places, rather than restructuring the app", though. – Steve Jessop Nov 04 '10 at 22:42
  • @Steve: an access function, a class. – Clifford Nov 04 '10 at 22:57
  • @Clifford: those are still globals for the purpose of all the arguments I make about globals. The problem is shared state, accessible from anywhere by use of a universally-visible name. Where "universe" might be the whole program or maybe just the TU, as indicated in my footnote. When I say globals, I don't just mean objects with names in global scope. Those are the hardest to reason about, perhaps, but other kinds of globals are also (typically) hard to reason about. – Steve Jessop Nov 04 '10 at 23:23
1

The reason i never do it is because it creates a mess. Imagine setting ALL unique variables to globals, you would have an external list the size of a phonebook.

Another reason could be that you don't know where it is initialized or modified. What if you accidently modify it at place X in file Y? You will never know. What if it isn't initialized yet? You will have to check everytime.

if (global_var = 0) // uh oh :-(
if (object->Instance() = 0) // compile error :-)

This can both be fixed using singletons. You simply cant assign to a function returning you the object's adress.

Besides that: you don't need your screen buffer everywhere in your application, however if you want to: go ahead, it doesn't make the program run less good :-)

And then you still have the namespace problem but that at least gives you compile errors ;-)

Mike DeSimone
  • 41,631
  • 10
  • 72
  • 96
DoXicK
  • 4,784
  • 24
  • 22
1

"Why not": global variables give you spaghetti information flow.

That's the same as goto gives you spaghetti control flow.

You don't know where anything comes from, or what can be assumed at any point. The INTERCAL solution of introducing a come from statement, while offering some initial hope of finally being sure of where control comes from, turned out to not really solve that problem for goto. Similarly, more modern language features for tracking updates to global variables, like onchangeby, have not turned out to solve that problem for global variables.

Cheers & hth.,

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
0

Global variables (and singletons, which are just a wrapper around a global variable) can cause a number of problems, as I discussed in this answer.

For this specific issue -- blittable objects in a game kit -- I'd be apt to suggest a method signature like Sprite::drawOn(Canvas&, const Point&). It shouldn't be excessive overhead to pass a reference to the Canvas around, since it's not likely to be needed except in the paint pathway, and within that pathway you're probably iterating over a collection anyway, so passing it in that loop isn't that hard. By doing this, you're hiding that the main program has only one active screen buffer from the sprite classes, and therefore making it less likely to create a dependency on this fact.

Disclaimer: I haven't used SDL itself before, but I wrote a simple cross-platform C++ game kit back in the late '90s. At the time I was working on my game kit, it was fairly common practice for multi-player X11-based games to run as a single process on one machine that opened a connection to each player's display, which would quite efficiently make a mess of code that assumed the screen buffer was a singleton.

Community
  • 1
  • 1
Jeffrey Hantin
  • 35,734
  • 7
  • 75
  • 94
  • That is a somewhat simplistic notion of a *singleton*; an *access function* might be a wrapper for a static (not global), but the singleton pattern is far more than that. Even a simple access function enforces single point access (somewhere you can place a breakpoint) and enables access control and data validation, so is far safer than a global variable and should not be dismissed as being equally as bad. – Clifford Nov 04 '10 at 22:54
  • I go into more detail in the linked answer; the crux of my gripe with singletons is the existence of a static access point to mutable state, regardless of what other services that access point provides. – Jeffrey Hantin Nov 04 '10 at 23:29
  • 1
    @Clifford: no, a singleton is not "equally bad". it is far, far worse than globals. Globals have their uses. I've never ever seen a justifiable use of a singleton. If you want a single point of access to your global object, then create that. But you don't need to make it a singleton. http://jalf.dk/blog/2010/03/singletons-solving-problems-you-didnt-know-you-never-had-since-1995/ – jalf Nov 05 '10 at 01:54
  • @jalf: I never advocated the singleton pattern as an alternative to a global variable; that is not what a singleton is for. It is a pattern intended to prevent multiple instances of an object - no more, and is useful for that purpose - (when implemented correctly, therein lies the problem perhaos). I do not recognize the apparent association with globals made here. Regarding globals having uses; see the article linked in my posted answer. I have never needed to implement a singleton in a desktop application; but I work in embedded systems, and they are rather useful in that domain. – Clifford Nov 05 '10 at 09:32
  • Give me an example of when it is useful to hardcode a "one instance" limitation then. I've never come across such a case. I have encountered many cases where I need one instance of an object. But never ever anything that warranted making this a compile-time guarantee. – jalf Nov 05 '10 at 12:40
0

In this case a class that provides member functions for all methods that need access to the screen buffer would be a more OOP friendly approach. Why should everyone and any one have uncontrolled access to it!?

As to whether there are times when a global is better or even necessary, probably not. They are deceptively attractive when you are hacking out some code, because you need jump through no syntactic hoops to access it, but it is generally indicative of poor design, and one that will rapidly atrophy inter maintenance and extension.

Here's a good read on the subject (related to embedded programming, but the points apply to any code, it is just that some embedded programmers thing they have a valid excuse).

Clifford
  • 88,407
  • 13
  • 85
  • 165
-2

I'm also curious to hear the precise explanation, but I can tell you that the Singleton pattern usually works pretty well to fill the role of global variables.

suszterpatt
  • 8,187
  • 39
  • 60
  • 5
    A singleton is just a global in a fancy dress. So why bother with it? Just use the global... – Zan Lynx Nov 04 '10 at 22:14
  • 1
    Singleton provides with encapsulation and more control over the object, so it's not just a global variable - it is a layer of logic transparent to the caller. – Leonid Nov 04 '10 at 22:23
  • @Zan: Which is exactly the sort of seemingly valid logic that makes me curious as to why not to use globals afterall. – suszterpatt Nov 04 '10 at 22:24
  • @Zan: I like it. "Singletons - OK on Halloween, the rest of the year you just look crazy". – Steve Jessop Nov 04 '10 at 22:25
  • @Leonid: A singleton provides *less* control, more threading difficulties, and a completely unrelated and potentially crippling restriction. Which is an absurd thing to suggest as a replacement for a plain old global. – jalf Nov 05 '10 at 01:52
  • 1
    @jalf: What I meant to say is more control over *initialization* of the object, from the caller's perspective it won't give more control, however provide a layer of logic and improve maintainability. Especially if the object wrapped in a singleton is 3rd party code. Perhaps the most use of Singleton would be when the type of global instance is abstract and concrete instance is likely to change. Then it would serve more as a factory for the object. For threading non-thread safe singleton is no different from a non-thread safe global. – Leonid Nov 05 '10 at 09:08
  • The difference between a singleton and a global shows up in C++, because it doesn't give you control over the order of initialization of global objects and/or static member objects. The only thing you can be sure of is that POD objects (like pointers) will be initialized before non-POD objects (e.g. classes with constructors). So if you have a global `Logger`, and a global `DisplayManager`, the `DisplayManager` may want to use the `Logger` during its initialization. If they are globals, there is no guarantee the `Logger` gets initialized first. If they're both singletons, it Just Works. – Mike DeSimone Nov 07 '10 at 21:00