This final chapter of Part II is a direct continuation of the previous one on functions. It wraps up this section on the fundamentals of C in style — before we dive into more advanced concepts.
By now, you've learned that a real C program is made up of lots of functions. Each one does a specific job and usually returns a result. And by piecing them all together, you can build just about any program you want :)
But up until now, we've been working inside a single file called main.c
. That was fine while our programs were small, but soon you'll be writing programs with dozens — hundreds — of functions. And if you try stuffing them all into one file, it'll become an unreadable mess!
That's exactly why modular programming was invented. The idea is super simple: instead of dumping all your code into one file (main.c
), you split it up across several smaller files (lego style).
Function Prototypes
So far, I've always asked you to write your function before the main function.
Why?
Because order matters here: if you put your function before main, your computer will have already seen it and will "know" about it. So when you call that function from main, it knows exactly where to look.
But if your function comes after main, it won't work — because your computer hasn't "met" that function yet. Try it out and see for yourself ;)
Kinda lame, right?
Totally agree with you! :D But don't worry — programmers figured this out a long time ago and came up with a fix ;)
Thanks to what I'm about to show you, you'll be able to write your functions in any order in your source code. Way better, right? Trust me on this ^^
The Prototype: Declaring a Function
We're going to declare our functions to the computer by writing what's called a prototype. Don't let the fancy name scare you — it's actually super simple ;)
Let's take the first line of our areaRectangle
function:
double areaRectangle(double width, double height) { return width * height; }
Now, copy just that first line (double areaRectangle...) and paste it at the top of your source file, just after your #includes.
Then, add a semicolon at the end of the line.
That's it! You can now move your areaRectangle
function below main if you want ;)
Here's what your code should look like:
#include <stdio.h> #include <stdlib.h>// The line below is the prototype for the areaRectangle function: double areaRectangle(double width, double height);
int main(int argc, char *argv[]) { printf("Rectangle 5x10. Area = %f\n", areaRectangle(5, 10)); printf("Rectangle 2.5x3.5. Area = %f\n", areaRectangle(2.5, 3.5)); printf("Rectangle 4.2x9.7. Area = %f\n", areaRectangle(4.2, 9.7)); return 0; }
// Our areaRectangle function can now be placed anywhere in the code: double areaRectangle(double width, double height) { return width * height; }
What's changed here is the addition of that prototype line at the top of the file.
A prototype is basically a heads-up to the computer. It says: "Hey, there's a function called areaRectangle. It takes these parameters and returns this type of result."
That helps the computer keep things organized.
Thanks to that one line, you're now free to rearrange your functions however you like — no more stress about order ;)
You may have noticed that the main function doesn't have a prototype. That's normal — it's the only one that doesn't need one. Why? Because your computer already knows it. It's always the same, so by now, it's burned into memory.
Variable Names Are Optional
In your prototype, it's optional to include the names of the parameters. All the computer really cares about are the types.
So instead of:
double areaRectangle(double width, double height);
You could just write:
double areaRectangle(double, double);
Both work just fine.
But the version I showed you earlier has one big advantage: you can just copy-paste the first line of your actual function and slap a semicolon on the end. Boom, done. Super quick and easy ;)
One Last Thing: Don't Forget the Semicolon!
Never forget the semicolon at the end of a prototype. That's how the computer tells the difference between a prototype and the start of a real function.
If you forget it, your compiler might throw some really confusing errors your way.
Header Files
Up until now, we've only been working with a single source file in our project — the one I asked you to name main.c.
Multiple Files Per Project
In practice, your programs won't all live inside that one lonely main.c. I mean, you can do it that way, sure — but scrolling through a 10,000-line file? Not super convenient (well, I don't think so anyway :p).
That's why most of the time, we split our code across multiple files in a project.
You didn't already forget, did you? o_O Okay, fine, let me explain again — it's important that we're all on the same page here.
A project is just the collection of source files that make up your program.
So far, our projects have only had one source file. If you look in your IDE (usually on the left side), you'll probably just see a single file: main.c
.
But it's totally possible to have a project made of several files: main.c
, character_class.c
, attack_sequence.c
, combat_resolution.c
, and so on.
You'll recognize main.c
in that list — it's the one that holds your main function. Personally, I like to keep main.c
strictly for the main function and nothing else (but that's just my style — you do you!).
That's up to you ;)
Usually, we group functions by topic into separate files. So in character_class.c
, you'd find all the functions related to managing the player character. In attack_sequence.c
, you'd have all the attack logic, and so on.
You don't have to go that far! It's all about balance. Try to organize related functions together in the same file — enough to keep things tidy, but not so many that it gets overwhelming. Too many files and you'll get lost... but too few and each file will be a monster. Find the sweet spot ;)
.h and .c Files
In C, the compiler works with two kinds of files:
.h
files: called header files. These hold function prototypes..c
files: source files. These hold the actual function definitions.
So, while we've been putting prototypes in our .c
files (like we did in main.c earlier), that's not how things are usually done — unless the program is tiny.
Normally, for every .c
file, you'll create a matching .h
file to hold the prototypes.
So you might have:
- character_class.c and character_class.h
- attack_sequence.c and attack_sequence.h
- And so on...
You include the .h
file using a preprocessor directive. Okay, buckle up — this is where things start to click :D
You already know how to do it!
Check out the top of my character_class.c
file, for example:
#include <stdlib.h> #include <stdio.h> #include "character_class.h"void create_character() { // ... }
That #include directive? Yep, that's the one you've been using all along :)
Let's break it down:
#include <stdlib.h> #include <stdio.h> #include "character_class.h"
We're including three .h files: stdio, stdlib, and our own character_class.h.
Notice something? Files you create go in quotes ("..."
), and library files go in angle brackets (<...>
).
So:
- Use < > for files located in your IDE's "include" directory
- Use " " for files that live in your project folder (next to your .c files)
When the compiler sees #include "character_class.h", it basically pastes the contents of that file right there.
So what's in character_class.h
?
Just the prototypes for the functions in character_class.c
!
/* character_class.h -----By Jace, for SiteRaw (www.siteraw.com)
Role: prototypes of character class functions. */
void create_character(); void move_character(int x, int y); void delete_character();
That's how a real C project is structured ;)
Simple reason: whenever you call a function, the compiler needs to know it exists, what parameters it takes, what it returns, etc. That's what a prototype is for — it's like an instruction manual for the function.
It all comes down to order. If you include the prototypes via a .h file at the top of your .c, then the compiler has all the info it needs before reading the function calls.
That means you don't have to worry about the order of functions in your .c files anymore. Clean and simple!
If you write a small program with 2–3 functions, it might seem like prototypes are optional (because it compiles fine). But that won't last. As soon as your program gets a little more complex and you forget to include prototypes in a header file... boom, compilation errors :p
For example, if you're calling a function from functions.c inside main.c, you'll need to include its prototypes in main.c by adding #include "functions.h" at the top.
Golden rule: every time you call function X in a file, that file needs access to X's prototype — either written directly or included via a header. This lets the compiler double-check that you're calling the function correctly (right number and type of parameters, etc.).
Depends on your IDE, but the process is more or less the same:
Go to File > New > Source File
.
This will open a new empty file. It won't be a .c or .h just yet — you have to save it first.
So save the file (even if it's still empty). It'll ask you to name the file. That's where you choose the extension:
- Name it file.c, and you've got a .c file.
- Name it file.h, and you've got a .h file.
That's all there is to it ;) Save the file in the same folder as your other project files — same place as main.c. Usually, all your .c and .h files will live in the same directory.
If your files are listed alphabetically, you'll see both .c and .h side by side.
About Standard Library Includes
You might be wondering...
Yep, absolutely!
They're installed with your IDE, usually inside a folder called include.
There, you'll find tons of files — the standard library headers (.h files), which are available on all systems (Windows, Mac, Linux...). You'll spot familiar names like stdio.h and stdlib.h.
You can open them if you're curious, but... maybe keep a bucket nearby just in case :-°
They can be a little overwhelming — lots of unfamiliar things like preprocessor directives. If you dig through, you'll see it's mostly full of standard function prototypes like printf.
Ah! That's the thing — they don't exist :D
The .c files for standard functions are already compiled into binary (machine code), so you'll never actually see their source.
The compiled files usually live in a folder named something like lib (short for "library").
In Visual Studio, these compiled library files have a .lib extension, but other compilers use different ones.
And there you go! Now you have a much better understanding of how it all fits together.
In your .c files, you include the .h files for standard libraries so you can use their functions (like printf). With the prototypes available, the compiler can check if you're calling those functions the right way — no missing parameters or mismatched types.
Separate Compilation
Now that you know a project is made up of several source files, we can dig a little deeper into how compilation actually works. Up to now, we've kept things super simple.
Here's a more accurate diagram of what happens during compilation. Don't worry, I'll walk you through it step by step. Let's go! ^^
1. Preprocessor
The preprocessor is a program that runs before the actual compilation. Its job is to process special instructions — those famous lines starting with #, known as preprocessor directives.
So far, we've only used #include
, which lets us include the contents of one file into another. The preprocessor can actually do a lot more, but we'll get into that later. For now, just remember: #include
is the most important one. ;)
The preprocessor literally replaces every #include line with the full content of the file you specified. That means it sticks the contents of your .h files right into your .c files.
At this stage, your .c file is fully loaded with everything it needs — all the function prototypes, everything. It's now bigger than it was before, but also more complete.
2. Compilation
This is the big step. The compiler takes your (now preprocessed) source code and translates it into binary code the computer can understand.
But here's the key thing: it compiles each .c file one by one. It will go through every single source file in your project — this is why it's important to have added all your files to the project properly (they should all show up in that little list on the left of your IDE ;) ).
For each .c file it compiles, the compiler creates a temporary binary file: usually a .o or .obj, depending on which compiler you're using.
These .o files are intermediate files, not the final executable. Most of the time, they're deleted automatically after compilation is done — but some compilers let you keep them if you really want (no real reason to though).
3. Linking
Now comes the linker (aka the "link editor"). Its job is to take all those .o files and combine them into one big final file: your executable!
On Windows, this executable ends with .exe
. On other operating systems, it'll have whatever extension is standard for that platform.
And that's it! Now you know exactly what happens behind the scenes :D Seriously, this diagram is super important. It's what separates a weekend copy-paster from someone who actually understands what they're doing ;)
Most errors happen during compilation, but linker errors are also a thing. That usually means the linker couldn't find all the .o files it needed — maybe you forgot to include one.
What about when you use libraries?
Okay, there's still one piece missing from our diagram: libraries!
Well, the first part stays exactly the same. The only difference is that the linker has a bit more work to do. Along with gluing together your own .o files, it now also needs to add the compiled library files your program depends on. These usually have a .lib
or .a
extension depending on your compiler.
So the linker takes your .o
files plus the library .lib files you're using, and bundles them all together into the final executable.
That's how we end up with a 100% complete program — one that contains all the instructions the computer needs, even those that tell it how to display text on the screen!
Take printf, for example. That function lives inside a .lib
file, and it gets added into your executable right alongside your own code.
A bit later on, we'll learn how to use graphics libraries. These, too, come in .lib
files and contain the instructions that tell your computer how to open a window, draw images, and all that fun stuff.
But hang tight — we'll get there soon enough. As the saying goes, good things come to those who wait ;)
Scope of Functions and Variables
To wrap up this chapter, let's talk about what we call the scope of functions and variables.
We're going to see when variables and functions are accessible — that is, when you can actually use them.
Variables Local to Functions
When you declare a variable inside a function, it gets wiped from memory as soon as the function ends:
int triple(int number) { int result = 0; // The variable 'result' is created in memory result = 3 * number; return result; } // The function ends here, and 'result' is removed from memory
So a variable declared inside a function only exists while that function is running.
What does that mean in practice? It means you can't access it from another function!
int triple(int number); int main(int argc, char *argv[]) { printf("The triple of 15 is %d\n", triple(15)); printf("The triple of 15 is %d", result); // This line will crash during compilation! return 0; } int triple(int number) { int result = 0; result = 3 * number; return result; }
Here in main, I'm trying to access the variable result. But since result was created inside the function triple, it's not visible to main.
So remember: a variable declared inside a function is only accessible within that function. We call this a local variable.
Global Variables: Use with Caution
A Global Variable Accessible from Any File
It is possible to declare variables that can be accessed from any function in any file of your project. I'll show you how, just so you're aware — but this is something you should generally avoid doing. It may seem like it makes your life easier at first, but eventually you'll end up with too many variables accessible from everywhere, which can become a nightmare to manage.
To create a "global" variable that's available everywhere, just declare it outside of all functions — typically at the top of the file, after the #include statements:
#include <stdio.h> #include <stdlib.h> int result = 0; // Global variable declaration void triple(int number); // Function prototype int main(int argc, char *argv[]) { triple(15); // Calls the triple function, which modifies the global 'result' printf("The triple of 15 is %d\n", result); // We have access to 'result' return 0; } void triple(int number) { result = 3 * number; }
In this example, the triple function no longer returns anything (void). It simply modifies the global result variable, which main can then use.
This result variable is now visible to every file in your project — any function, anywhere, can access it.
But again, this approach is generally discouraged in C. It's better to return a value from a function using return, and pass it around explicitly.
A Global Variable Limited to One File
The global variable we just saw is visible across all files in your project.
You can do that! It's still considered global, but it's only accessible to the functions inside the same file — not the whole program.
To achieve that, just add the static keyword in front of the variable declaration:
static int result = 0;
Static Variable Inside a Function
If you place the static keyword in front of a variable inside a function, it means something else entirely.
A static local variable doesn't get deleted at the end of the function. When the function is called again later, that variable still holds the same value as before.
For example:
int triple(int number) { static int result = 0; // Created the first time the function is called result = 3 * number; return result; } // 'result' is NOT removed when the function ends
It means the next time you call triple, the variable result will still contain the value it had the last time you called it.
Here's a small example to really make it clear:
int increment(); int main(int argc, char *argv[]) { printf("%d\n", increment()); printf("%d\n", increment()); printf("%d\n", increment()); printf("%d\n", increment()); return 0; } int increment() { static int number = 0; number++; return number; }
Output:
1 2 3 4
In this case, the first time increment is called, the variable number is created and set to 0. It's incremented to 1, and then the function ends — but the variable sticks around.
Next time the function is called, it reuses the same number. It doesn't create it again. Since it was 1, now it becomes 2, then 3, then 4, and so on...
This kind of variable isn't used all that often, but it can come in handy — so I wanted to make sure you knew about it ;)
Functions Local to a File
Let's finish our talk on scope with function scope.
By default, when you create a function, it's globally accessible to your entire program. That means it can be called from any other .c file in the project.
But sometimes, you might want to create functions that are only accessible from the same file they're defined in.
To do that, just add the static keyword (yes, again) in front of the function:
static int triple(int number) { // Some code here }
And don't forget to update the prototype as well:
static int triple(int number);
And there you go! Now your static function triple can only be called by other functions in the same file (like main.c, for example). If you try to call it from another file (like display.c), it won't work — because triple won't be visible there ;)
Let's Recap!
Variable Scope
- A variable declared inside a function is deleted when the function ends. It's only accessible within that function.
- A variable declared inside a function with static isn't deleted when the function ends — it keeps its value throughout the program's execution.
- A variable declared outside all functions is global, accessible from any function in any source file of the project.
- A global variable declared with static is only accessible from the file it was declared in. Other files can't see it.
Function Scope
- By default, a function is accessible from any file in the project — you can call it from anywhere.
- If you want a function to be private to one file, add the static keyword in front of it.
Enjoyed this C / C++ course?
If you liked this lesson, you can find the book "Learn C Programing for Beginners" from the same authors, available on SiteRaw, in bookstores and in online libraries in either digital or paperback format. You will find a complete C / C++ workshop with many exclusive bonus chapters.