8. Structures


You've been messing with variables for quite some time now, and you've made a bunch of them here and there to do stuff, yes? Usually you bundle them into the same basic block and let it go at that.

Sometimes, though, it makes more sense to put them together into a structure. This is a construct that allows you to logically (or even illogically, if the fancy takes you) group variables into, uh, groups. You can then reference the group as a whole. One place this comes in really handily is if you want to pass 13 bazillion parameters to a function, but don't want the function declaration to be that long. You just put all the variables into a structure (or struct, as it is normally called), and then pass that structure to the function. (Actually, people usually pass a pointer to the structure, but we'll get to that later.)

So how do you use one of these things? First off, the struct itself is a new type. If you make a variable that is a struct foo, it's type is "struct foo", just as the number 12 is of type "int". This is a little bit spooky because this could be the first time you've created a new type, and it might be unclear how that works.

Adding to the confusion, it turns out there are multiple ways to create and use a new struct type, and barely any of them are particularly intuitive. We'll have an example of one way to declare a new struct here:

#include <stdio.h>

/* Here we declare the type so we can use it later: */
struct stuff {
    int val;
    float b;
};

/* Note that we don't actually have any variables of that type, yet. */

int main(void)
{
    /* ok, now let's declare a variable "s" of type "struct stuff" */
    struct stuff s;


    s.val = 3490;  /* assignment into a struct! */
    s.b = 3.14159;

    printf("The val field in s is: %d\n", s.val);

    return 0;
}

The compiler allows us to predeclare a struct like in the example. We can then use it later, like we do in main() to assign values into it. When we say things like s.val = 3490, we are using a special operator to access the val field, known as the dot operator (.).

8.1. Pointers to structs

Now let's talk a bit about how to pass these structs around to other functions and stuff. I mentioned before that you probably want to pass a pointer to the struct instead of the struct itself. Why?

Don't you hate it when the professor asks you a question like that, but you're too tired in lecture to care to begin to think about it? "Just tell me and we'll get on with it," is what you're thinking, isn't it.

Fine! Be that way! Well, remember that when you pass parameters to functions, and I'll clear my throat here in preparation to say again, EVERY PARAMETER WITHOUT FAIL GETS COPIED ONTO THE STACK when you call a function! So if you have a huge struct that's like 80,000 bytes in size, it's going to copy that onto the stack when you pass it. That takes time.

Instead, why not pass a pointer to the struct? I hear you--doesn't the pointer have to get copied on the stack then? Sure does, but a pointer is, these days, only 4 or 8 bytes, so it's much easier on the machine, and works faster.

And there's even a little bit of syntactic sugar to help access the fields in a pointer to a struct. For those that aren't aware, syntactic sugar is a feature of a compiler that simplifies the code even though there is another way to accomplish the same thing. For instance, I've already mentioned the += a number of times...what does it do? Did I ever tell you? To be honest, in all the excitement, I've forgotten myself. Here is an example that shows how it works like another two operators, but is a little bit easier to use. It is syntactic sugar:

i = i + 12;  /* add 12 to i */
i += 12;     /* <-- does exactly the same thing as "i = i + 12" */

You see? It's not a necessary operator because there's another way to do the same thing, but people like the shortcut.

But we are way off course, buster. I was talking about pointers to structs, and here we are talking about how to access them. Here's an example wherein we have a struct variable, and another variable that is a pointer to that struct type, and some usage for both (this will use struct stuff from above):

#include <stdio.h>

/* Here we declare the type so we can use it later: */
struct antelope {
    int val;
    float something;
};

int main(void)
{
    struct antelope a;
    struct antelope *b; /* this is a pointer to a struct antelope */

    b = &a; /* let's point b at a for laughs and giggles */

    a.val = 3490; /* normal struct usage, as we've already seen */

    /* since b is a pointer, we have to dereference it before we can */
    /* use it:                                                       */

    (*b).val = 3491;

    /* but that looks kinda bad, so let's do the exact same thing  */
    /* except this time we'll use the "arrow operator", which is a */
    /* bit of syntactic sugar:                                     */

    b->val = 3491; /* EXACTLY the same as (*b).val = 3491; */

    return 0;
}

So here we've seen a couple things. For one, we have the manner with which we dereference a pointer to a struct and then use the dot operator (.) to access it. This is kind of the classic way of doing things: we have a pointer, so we dereference it to get at the variable it points to, then we can treat it as if it is that variable.

But, syntactic sugar to the rescue, we can use the arrow operator (->) instead of the dot operator! Saves us from looking ugly in the code, doesn't? The arrow operator has a build-in deference, so you don't have to mess with that syntax when you have a pointer to a struct. So the rule is this: if you have a struct, use the dot operator; if you have a pointer to a struct, use the arrow operator (->).

8.2. Passing struct pointers to functions

This is going to be a very short section about passing pointers to structs to functions. I mean, you already know how to pass variables as paramters to functions, and you already know how to make and use pointers to structs, so there's really not much to be said here. An example should show it straightforwardly enough. ("Straightforwardly"--there's an oxymoronic word if ever I saw one.)

Grrrr. Why do I always sit on this side of the bus? I mean, I know that the sun streams in here, and I can barely read my screen with the bright light blazing away against it. You think I'd learn by now. So anyway...

#include <stdio.h>

struct mutantfrog {
    int num_legs;
    int num_eyes;
};

void build_beejs_frog(struct mutantfrog *f)
{
    f->num_legs = 10;
    f->num_eyes = 1;
}

int main(void)
{
    struct mutantfrog rudolph;

    build_beejs_frog(&rudolph);  /* passing a pointer to the struct */

    printf("leg count: %d\n", rudolph.num_legs); /* prints "10" */
    printf("eye count: %d\n", rudolph.num_eyes); /* prints "1" */

    return 0;
}

Another thing to notice here: if we passed the stuct instead of a pointer to the struct, what would happen in the function build_beejs_frog() when we changed the values? That's right: they'd only be changed in the local copy, and not back at out in main(). So, in short, pointers to structs are the way to go when it comes to passing structs to functions.

Just a quick note here to get you thinking about how you're going to be breaking stuff down into basic blocks. You now have a lot of different tools at your disposal: loops, conditionals, structs, and especially functions. Remember back on how you learned to break down projects into little pieces and see what you'd use these individual tools for.