Multi-File Programming

Multi-file programming is like team collaboration — declarations are contracts (header files), implementations are the work (source files), extern is a cross-department reference, and Makefile is the project schedule.

Why Multi-File

A single .c file is enough for small programs, but real projects have large codebases that need to be split by functionality into multiple files:

Writing Header Files

Header files (.h) contain declarations, and source files (.c) contain definitions. This is the most fundamental modularization rule in C.

Header File Content

C
#ifndef MATH_UTILS_H
#define MATH_UTILS_H

int add(int a, int b);
int subtract(int a, int b);
double average(int *arr, int n);

#endif

Source File Content

C
#include "math_utils.h"

int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

double average(int *arr, int n) {
    if (n <= 0) return 0.0;
    int sum = 0;
    for (int i = 0; i < n; i++) sum += arr[i];
    return (double)sum / n;
}

Main Program

C
#include <stdio.h>
#include "math_utils.h"

int main(void) {
    printf("3 + 5 = %d\n", add(3, 5));
    printf("10 - 4 = %d\n", subtract(10, 4));
    int scores[] = {80, 90, 75, 88};
    printf("Average: %.1f\n", average(scores, 4));
    return 0;
}
💡 Tip: Header files should only contain declarations (function prototypes, type definitions, macros, extern declarations), not definitions (function bodies, variable initializations). Duplicate definitions cause linker errors.

extern for Cross-File References

A global variable defined in one .c file can be accessed from other files using extern.

Definition (in some .c file)

C
int g_count = 0;

Declaration (in a header or another .c file)

C
extern int g_count;

extern tells the compiler "this variable is defined elsewhere — this is only a declaration," so no new space is allocated.

Example

Project structure:

TEXT
project/
├── counter.h
├── counter.c
└── main.c
▶ Try it Yourself

counter.h:

C
#ifndef COUNTER_H
#define COUNTER_H

extern int g_count;
void increment(void);
int get_count(void);

#endif

counter.c:

C
#include "counter.h"

int g_count = 0;

void increment(void) {
    g_count++;
}

int get_count(void) {
    return g_count;
}

main.c:

C
#include <stdio.h>
#include "counter.h"

int main(void) {
    increment();
    increment();
    increment();
    printf("count = %d\n", get_count());
    printf("g_count = %d\n", g_count);
    return 0;
}
TEXT
count = 3
g_count = 3
⚠️ Note: Do not include an initializer in an extern declaration. extern int g_count = 0; may become a definition on some compilers, causing duplicate definition errors.

Multi-File Compilation Commands

BASH
gcc -c counter.c -o counter.o
gcc -c main.c -o main.o
gcc counter.o main.o -o myapp

The -c flag compiles without linking, producing .o object files. All .o files are then linked into the final executable.

Compile All at Once

BASH
gcc counter.c main.c -o myapp

Simple, but changing any file requires recompiling everything — not suitable for large projects.

💡 Tip: The advantage of individual compilation: if you change main.c, you only need gcc -c main.c and then link, without recompiling counter.c.

Makefile Basics

Makefile automates compilation rules and only recompiles files that have been modified.

MAKEFILE
CC = gcc
CFLAGS = -Wall -g

myapp: main.o counter.o
	$(CC) $(CFLAGS) main.o counter.o -o myapp

main.o: main.c counter.h
	$(CC) $(CFLAGS) -c main.c -o main.o

counter.o: counter.c counter.h
	$(CC) $(CFLAGS) -c counter.c -o counter.o

clean:
	rm -f *.o myapp
⚠️ Note: Makefile indentation must use Tab characters, not spaces. This is the most common mistake for beginners.

Usage:

BASH
make
make clean

Makefile rule format:

MAKEFILE
target: dependencies
	command

Preventing Duplicate Inclusion

A header file may be included by multiple source files, and the same source file may indirectly include it multiple times. Without protection, duplicate declarations cause errors.

Approach 1: Include Guard

C
#ifndef MYHEADER_H
#define MYHEADER_H

void my_func(void);

#endif

On first inclusion, MYHEADER_H is undefined, so the content is processed and the macro is defined. On subsequent inclusions, #ifndef evaluates to false and the entire content is skipped.

Approach 2: #pragma once

C
#pragma once

void my_func(void);

Concise and supported by most compilers, but not part of the standard — theoretically less portable than include guards.

💡 Tip: Both approaches are used in practice. #pragma once is more concise; include guards are more standard. Pick one and use it consistently.

Header File Organization Principles

❓ FAQ

Q Can I put variable definitions in a header file?
A No. If the header is included by multiple .c files, it causes duplicate definitions. Variable definitions belong in .c files; use extern declarations in .h files.
Q What's the difference between extern and directly including a header?
A #include inserts the entire header file content; extern explicitly declares that a variable or function is defined elsewhere. Headers typically contain extern declarations — the two work together.
Q Does Makefile indentation really require Tab?
A Yes, this is a hard requirement of Make. Using spaces will cause errors. Configure your editor to use Tab indentation for Makefiles.
Q Should I choose #pragma once or include guards?
A Pick one and use it consistently within your project. #pragma once is simpler but non-standard; include guards are standard but slightly more verbose. Large open-source projects tend to use include guards.

📖 Summary

📝 Exercises

  1. Create a 3-file project: string_utils.h, string_utils.c, and main.c. Implement a string reversal function and a character-count function, then call and test them in main
  2. Write a Makefile for exercise 1 that supports make to compile and make clean to clean up
  3. Intentionally omit duplicate-inclusion protection in a header file, then #include the same header twice in main.c and observe the compilation error
100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏