6. Variables, The Sequel


Just when you thought it was safe to know everything there was to know about variables, this section of the guide lunges at you from the darkness! What?! There's more?

Yes, I'm sorry, but I'm afraid there is. We're going to talk about a couple things in this section that increase the power you have over variables TO THE EXTREME. Yes, by now you realize that melodrama is a well-respected part of this guide, so you probably weren't even taken off-guard by that one, ironically.

Where was I? Oh, yes; let's talk about variable scope and storage classes.

6.1. "Up Scope"

You recall how in some of those functions that we previously defined there were variables that were visible from some parts of the program but not from others? Well, if you can use a variable from a certain part of a program, it's said to be in scope (as opposed to out of scope.) A variable will be in scope if it is declared inside the block (that is, enclosed by squirrley braces) that is currently executing.

Take a look at this example:

int frotz(int a)
{
    int b;

    b = 10; /* in scope (from the local definition) */
    a = 20; /* in scope (from the parameter list) */
    c = 30; /* ERROR, out of scope (declared in another block, in main()) */
}

int main(void)
{
    int c;

    c = 20; /* in scope */
    b = 30; /* ERROR, out of scope (declared above in frotz()) */

    return 0;
}

So you can see that you have to have variables declared locally for them to be in scope. Also note that the parameter a is also in scope for the function frotz()

What do I mean by local variables, anyway? These are variable that exist and are visible only in a single basic block of code (that is, code that is surrounded by squirrley braces) and, basic blocks of code within them. For instance:

int main(void)
{  /* start of basic block */
    int a = 5; /* local to main() */

    if (a != 0) {
        int b = 10; /* local to if basic block */
        
        a = b; /* perfectly legal--both a and b are visible here */
    }

    b = 12; /* ERROR -- b is not visible out here--only in the if */

    { /* notice I started a basic block with no statement--this is legal */
        int c = 12;
        int a; /* Hey!  Wait!  There was already an "a" out in main! */

        /* the a that is local to this block hides the a from main */
        a = c; /* this modified the a local to this block to be 12 */
    }

    /* but this a back in main is still 10 (since we set it in the if): */
    printf("%d\n", a);

    return 0;
}

There's a lot of stuff in that example, but all of it is basically a presentation of a simple rule: when it comes to local variables, you can only use them in the basic block in which they are declared, or in basic blocks within that. Look at the "ERROR" line in the example to see exactly what won't work.

Let's digress for a second and take into account the special case of parameters passed to functions. These are in scope for the entire function and you are free to modify them to your heart's content. They are just like local variables to the function, except that they have copies of the data you passed in, obviously.

void foo(int a)
{
    int b;

    a = b; /* totally legal */
}

6.1.1. Global variables

There are other types of variables besides locals. There are global variables, for instance. Sounds grand, huh. Though they're aren't exactly the chicken's pajamas for a number of reasons, they're still a powerful piece of the language. Wield this power with care, since you can make code that's very hard to maintain if you abuse it.

A global variable is visible thoughout the entire file that it is defined in (or declared in--more on that later). So it's just like a local, except you can use it from anyplace. I guess, then, it's rather not like a local at all. But here's an example:

#include <stdio.h>

/* this is a global variable.  We know it's global, because it's */
/* been declared in "global scope", and not in a basic block somewhere */
int g = 10;

void afunc(int x)
{
    g = x; /* this sets the global to whatever x is */
}

int main(void)
{
    g = 10;    /* global g is now 10 */
    afunc(20); /* but this function will set it to 20 */
    printf("%d\n", g); /* so this will print "20" */

    return 0;
}

Remember how local variables go on the stack? Well, globals go on the heap, another chunk of memory. And never the twain shall meet. You can think of the heap as being more "permanent" than the stack, in many ways.

Now, I mentioned that globals can be dangerous. How is that? Well, one thing you could imagine is a large-scale project in which there were a bazillion globals declared by a bazillion different programmers. What if they named them the same thing? What if you thought you were using a local, but you had forgotten to declare it, so you were using the global instead?

(Ooo. That's a good side note: if you declare a local with the same name as a global, it hides the global and all operations in the scope will take place on the local variable.)

What else can go wrong? Sometimes using global variables encourages people to not structure their code as well as they might have otherwise. It's a good idea to not use them until there simply is no other reasonable way to move data around.

Another thing to consider is this: does it actually make sense to have this data stored globally for all to see? For example, if you have a game where you use "the temperature of the world" in a lot of different places, that might be a good candidate for a global varible. Why? Because it's a pain to pass it around, and everyone has the same need for it.

On the other hand, "the temperature of this guy's little finger" might not be of so much interest to the rest of the universe. It'd be better to store that data in a way that's more associated with the guy than globally. We'll talk more later about associating data with things (nice and vague, huh?) later.

6.2. Storage Classes

What is a storage class? It's a class of storing variables.

You're welcome.

Don't get this confused with any C++ class, either, since it's not at all the same thing.

So what does a storage class declaration do? It tells the compiler where to store the data, such as on the stack or on the heap, or if variable data storage is already declared elsewhere.

"What?"

Let's just get some examples in there and it'll make more sense.

6.2.1. Gimme some static!

Ready? Here's an example: I'm sitting on BART (the Bay Area Rapid Transit subway) as I type this on my way home from work. There is a young couple happily canoodling each other in the seat in front of me, and it's completely distracting.

...Er, what. No, an example! Yes! Ok, here it is:

void print_plus_one(void)
{
    static int a=0; /* static storage class! */

    printf("%d\n", a);

    a++;  /* increment the static value */
}

int main(void)
{
    print_plus_one(); /* prints "0" */
    print_plus_one(); /* prints "1" */
    print_plus_one(); /* prints "2" */
    print_plus_one(); /* prints "3" */
    print_plus_one(); /* prints "4" */

    return 0;
}

What have we here? How can this magic be? Isn't that a local variable in print_plus_one() and doesn't it get allocated on the stack and doesn't it go away after the function returns? How can it possibly remember the value from the last call?

The answer: it uses the modern day magic that is the static keyword. This directive (given before the type) tells the compiler to actually store this data on the heap instead of the stack! Ooooo! Remember how the heap can be thought of as more permanent? Well, the value gets initialized once (in the definition), and it never gets initialized again, so all operations on it are cumulative.

You'll probably see more use for this later, but it's common enough that you should know about it.

6.2.2. Other Storage Classes

There are other storage classes, yes. The default is auto, which you never see in the wild since it's default.

Another is extern which tells the compiler that the definition of the variable is in a different file. This allows you to reference a global variable from a file, even if its definition is somewhere else. It would be illegal to define it twice, and since it's global it'd be nice to have it available across different source files.

I know, I know. It's hard to imagine now, but programs do get big enough to span multiple files eventually. :-)