C
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
29: Function Prototypes and Header Files
Up until now, we've been keeping everything in a single file. That works fine for tiny scripts, but in the real world, a single C file with 5,000 lines of code is a nightmare to maintain. To keep things sane, we split our logic into different files. This is where function prototypes and header files come in.
Let's build a small utility for calculating rectangle properties. I want to keep the math logic separate from the user interface in main.c.
The "Wrong Way" to Organize Functions
I'll start by writing a simple program. I'm going to put my logic function below main because I personally find it easier to read the high-level flow of the program first before diving into the implementation details.
#include <stdio.h>
int main() {
double width = 5.0, height = 10.0;
double area = calculate_area(width, height);
printf("Area: %.2f\n", area);
return 0;
}
double calculate_area(double w, double h) {
return w * h;
}
If you try to compile this, your compiler is going to complain. Depending on your settings, you'll get a warning or an error about an "implicit declaration of function." The problem is that C reads files from top to bottom. When the compiler hits calculate_area inside main, it hasn't seen the definition yet, so it just guesses what the function looks like. Usually, it guesses wrong, and that leads to bugs that are a pain to track down.
Fixing the Flow with Prototypes
To fix this, I need a function prototype. Think of a prototype as a "spoiler" for the compiler. It tells C: "Hey, there's a function called calculate_area coming later; it takes two doubles and returns a double. Trust me on this."
#include <stdio.h>
// This is the prototype
double calculate_area(double w, double h);
int main() {
double width = 5.0, height = 10.0;
double area = calculate_area(width, height);
printf("Area: %.2f\n", area);
return 0;
}
double calculate_area(double w, double h) {
return w * h;
}
Now it compiles perfectly. But we're still in one file. If I add ten more shapes, main.c becomes a mess again.
Moving Logic to a Dedicated Header and Source
The professional way to handle this is to move the implementation to a .c file and the "contract" (the prototype) to a .h file. Let's create rect_math.h and rect_math.c.
First, the header file. I'm adding "include guards" here (the #ifndef part). I've seen too many beginners skip this, only to have their program crash because a header was included twice in a complex project. These guards ensure the compiler only reads the declarations once.
// rect_math.h
#ifndef RECT_MATH_H
#define RECT_MATH_H
double calculate_area(double w, double h);
double calculate_perimeter(double w, double h);
#endif
Now, the implementation file. This is where the actual work happens. Notice I include my own header file here to make sure the definitions match the prototypes.
// rect_math.c
#include "rect_math.h"
double calculate_area(double w, double h) {
return w * h;
}
double calculate_perimeter(double w, double h) {
return 2 * (w + h);
}
Finally, my main.c becomes incredibly clean. I just include the header, and I have access to all the math functions without cluttering my primary logic.
// main.c
#include <stdio.h>
#include "rect_math.h"
int main() {
double w = 5.0, h = 10.0;
printf("Area: %.2f\n", calculate_area(w, h));
printf("Perimeter: %.2f\n", calculate_perimeter(w, h));
return 0;
}
Connecting the Pieces
One last thing: you can't just compile main.c anymore. The compiler knows calculate_area exists because of the header, but it doesn't know where the actual code is. You have to compile both C files together.
If you're using GCC, you'd run something like this:
gcc main.c rect_math.c -o rect_tool
The compiler handles the individual files, and the linker ties them together into one final executable. It's a bit more work upfront, but this is how actual software is built. It allows multiple people to work on different files simultaneously without stepping on each other's toes.
📋 Practical Task
Build a Circle Geometry Library
Your task is to create a small multi-file program that calculates properties of a circle. You must split the code across three specific files to practice the organization we just covered.
- circle_math.h: Define include guards and provide prototypes for two functions:
calculate_circle_areaandcalculate_circle_circumference. Both should take adouble radiusas an argument and return adouble. - circle_math.c: Implement the two functions. Use
3.14159as the value for PI. - main.c: Include the
circle_math.hheader. Ask the user to input a radius viascanf, then call your library functions to print the area and circumference.
Challenge: Try compiling the program using a single command (e.g., gcc main.c circle_math.c -o circle_tool) and ensure there are no "implicit declaration" warnings.
There are no comments for now.