C course menu
Header Files and Multi-file Programs in C: .h and .c Files, Linking and Makefiles
Split a C program across several files: what goes in a header (.h) and what goes in a source (.c) file, include guards, compiling and linking multiple files with gcc, static for private functions, extern for shared variables, and a simple Makefile.
On this page
Real C programs are split across many files. Each .c file is compiled separately into an object file, and the linker joins the object files into one program. Header files (.h) let the files agree on what each one provides.
Header and source files#
- A header (
.h) holds declarations: function prototypes, type definitions (struct,typedef,enum), macros andexternvariable declarations. It is the module's public interface. - A source file (
.c) holds definitions: function bodies and variables. It#includes its own header so the compiler checks that the two match.
Here is a small "bank account" module and a program that uses it.
#ifndef ACCOUNT_H
#define ACCOUNT_H
typedef struct {
char holder[32];
double balance;
} Account;
extern int accounts_opened; /* defined in account.c */
Account account_open(const char *holder, double initial);
int account_withdraw(Account *acc, double amount);
void account_print(const Account *acc);
#endif /* ACCOUNT_H */#include <stdio.h>
#include "account.h"
int accounts_opened = 0; /* the one definition */
static int valid_amount(double amount) { /* private to this file */
return amount > 0;
}
Account account_open(const char *holder, double initial) {
Account a;
snprintf(a.holder, sizeof a.holder, "%s", holder);
a.balance = initial;
accounts_opened++;
return a;
}
int account_withdraw(Account *acc, double amount) {
if (!valid_amount(amount) || amount > acc->balance) return 0;
acc->balance -= amount;
return 1;
}
void account_print(const Account *acc) {
printf("%s: %.2f\n", acc->holder, acc->balance);
}#include <stdio.h>
#include "account.h"
int main(void) {
Account a = account_open("Asha", 5000);
Account b = account_open("Ravi", 1200);
account_withdraw(&a, 750);
if (!account_withdraw(&b, 2000)) printf("Ravi: insufficient funds\n");
account_print(&a);
account_print(&b);
printf("%d accounts opened\n", accounts_opened);
return 0;
}Compiling and linking#
Compile all the source files together (headers are never compiled on their own; they are included):
gcc -std=c17 -Wall -Wextra main.c account.c -o bank
./bankRavi: insufficient funds
Asha: 4250.00
Ravi: 1200.00
2 accounts openedOr compile each file to an object file, then link. Only the changed files need recompiling:
gcc -std=c17 -Wall -Wextra -c account.c
gcc -std=c17 -Wall -Wextra -c main.c
gcc main.o account.o -o bankCompiler errors versus linker errors#
- Compiler errors come from one file: a syntax mistake, or a call that does not match a prototype.
- Linker errors come from joining files. Forgetting to compile
account.cgives anundefined reference to `account_open'error: the prototype promised the function, but no file defined it. Defining the same global function or variable in two files gives amultiple definitionerror.
static and extern across files#
- A function or global variable declared
staticin a.cfile is private to that file.valid_amountabove cannot be called frommain.c, and another file may use the same name without a clash. Make helper functionsstaticby default. - A global variable shared between files is defined once in a
.cfile (int accounts_opened = 0;) and declared withexternin the header. Shared globals should be rare.
What not to put in a header#
- Function bodies and variable definitions: every file that includes the header would define them again, causing
multiple definitionerrors. (static inlinefunctions are an exception.) - Unnecessary
#includes: include what the header itself needs, and put the rest in the.cfile.
A simple Makefile#
make rebuilds only what changed. A minimal Makefile for this project (the command lines must start with a tab):
CC = gcc
CFLAGS = -std=c17 -Wall -Wextra -g
bank: main.o account.o
$(CC) main.o account.o -o bank
main.o: main.c account.h
$(CC) $(CFLAGS) -c main.c
account.o: account.c account.h
$(CC) $(CFLAGS) -c account.c
clean:
rm -f *.o bankRun make to build and make clean to delete the build files. Larger projects often use CMake, which generates build files for many platforms.
Practice#
Practice exercise
Split a small "temperature" module into temp.h and temp.c, providing double c_to_f(double c) and double f_to_c(double f), plus a private helper. Write a main.c that converts 37 °C and 98.6 °F, and give the command to build it.
Show solution
#ifndef TEMP_H
#define TEMP_H
double c_to_f(double c);
double f_to_c(double f);
#endif#include "temp.h"
static double scale(double x, double factor, double offset) {
return x * factor + offset;
}
double c_to_f(double c) { return scale(c, 9.0 / 5.0, 32.0); }
double f_to_c(double f) { return scale(f - 32.0, 5.0 / 9.0, 0.0); }#include <stdio.h>
#include "temp.h"
int main(void) {
printf("%.1f F\n", c_to_f(37));
printf("%.1f C\n", f_to_c(98.6));
return 0;
}gcc -std=c17 -Wall -Wextra main.c temp.c -o temp98.6 F
37.0 CKey takeaway#
Finished this lesson?