Skip to Content
Course content

71: Multi-File Projects and Header Guards

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're running a professional kitchen. You have a menu that lists every dish the restaurant offers, but the menu doesn't actually contain the recipes—it just tells the customer (and the waiters) what is available. The actual recipes, the step-by-step instructions on how to cook the food, are kept in a separate recipe book in the back. If every single waiter carried a full copy of the recipe book in their pocket, it would be bulky, redundant, and a nightmare to update if the chef decided to change how the sauce is made.

In C, we do the exact same thing. The .h (header) file is your menu; it declares that a function or a struct exists. The .c (source) file is your recipe book; it contains the actual logic. When you start building projects larger than a single file, this separation is the only way to keep your sanity.

Dividing the Labor: .h vs .c

Let's say we're building a simple game engine. We need to handle a "Player" object. Instead of cramming everything into main.c, we'll split it up. I'll create player.h to tell the rest of the program what a Player looks like, and player.c to handle the actual movement logic.

Here is how the "menu" (player.h) looks:

typedef struct {
    int x, y;
    int health;
} Player;

void player_move(Player *p, int dx, int dy);
void player_heal(Player *p, int amount);

And here is the "recipe" (player.c), where we actually implement the functions. Notice that we include the header so the compiler can check that our definitions match our declarations:

#include "player.h"

void player_move(Player *p, int dx, int dy) {
    p->x += dx;
    p->y += dy;
}

void player_heal(Player *p, int amount) {
    p->health += amount;
}

Now, in main.c, I only need to #include "player.h". I don't need to know how player_move works; I just need to know it exists and what arguments it takes. This keeps the compilation process faster and the code much cleaner.

The Double-Include Disaster

Here is where things get tricky. As your project grows, you'll end up with headers that include other headers. For example, maybe you have a game_state.h that includes player.h, and then your main.c includes both game_state.h and player.h.

The C preprocessor is a bit blunt. When it sees #include, it literally copies and pastes the content of that file into the current one. If player.h gets pasted twice, the compiler sees the typedef struct Player twice. It will scream at you with a "redefinition" error. It doesn't matter that it's the exact same struct; C generally doesn't like seeing the same type defined twice in the same translation unit.

Locking the Door with Header Guards

To fix this, we use "Header Guards." Think of this as a sign on the kitchen door that says, "If you've already read the menu today, don't enter."

We wrap the entire contents of the header file in a conditional check. I've rewritten the player.h file below with guards. This is the industry standard way to do it:

#ifndef PLAYER_H
#define PLAYER_H

typedef struct {
    int x, y;
    int health;
} Player;

void player_move(Player *p, int dx, int dy);
void player_heal(Player *p, int amount);

#endif

Here's the play-by-play of what's happening:

  • #ifndef PLAYER_H: "If PLAYER_H is not defined..."
  • #define PLAYER_H: "...then define it immediately and proceed with the code."
  • The rest of the file is processed.
  • #endif: This closes the conditional block.

If the preprocessor encounters this file a second time, PLAYER_H is already defined. It will skip everything until the #endif, effectively ignoring the file. No redefinitions, no compiler tantrums. I've used the FILENAME_H convention here; it's a common pattern that keeps your guards unique across a large project.




📋 Practical Task

Implementing a Guarded Combat System

You are building a combat module for a RPG. You have three files, but the project won't compile because of redefinition errors. Your task is to fix the architecture by implementing proper header guards and separating declarations from definitions.

The Files:

  1. enemy.h: Currently contains the Enemy struct and the function prototype void enemy_take_damage(Enemy *e, int dmg);. It has no guards.
  2. enemy.c: Contains the actual logic for enemy_take_damage.
  3. combat.h: Includes enemy.h and declares a function void resolve_attack(Player *p, Enemy *e);. It has no guards.
  4. main.c: Includes both combat.h and enemy.h.

Your Goal:

  • Add unique header guards to enemy.h and combat.h.
  • Ensure enemy.c includes enemy.h.
  • Ensure combat.h includes enemy.h correctly.
  • Verify that main.c can include both headers without triggering a "redefinition of struct Enemy" error.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.