Separate declarations from implementation
The preprocessor runs before the compiler analyzes C expressions. Directives begin with # and control inclusion, macro expansion and conditional compilation. Headers normally publish declarations; source files supply function definitions.
A small two-file module
Create a header named arithmetic.h:
#ifndef ONLINECBT_ARITHMETIC_H
#define ONLINECBT_ARITHMETIC_H
int add(int a, int b);
#endifCreate arithmetic.c:
#include "arithmetic.h"
int add(int a, int b) {
return a + b;
}Create main.c:
#include
#include "arithmetic.h"
int main(void) {
printf("sum=%d\n", add(4, 5));
return 0;
} Build both source files together: gcc -std=c17 -Wall -Wextra -pedantic main.c arithmetic.c -o demo. Expected output: sum=9. Linking only main.c leaves the definition of add missing.
Why include guards exist
The #ifndef/#define/#endif pattern prevents a header's declarations from being processed repeatedly within one translation unit. Choose a unique guard name. Avoid names starting with reserved underscore patterns. A header is not normally a place for ordinary externally linked function definitions or initialized global objects, which can cause multiple-definition errors when included in several source files.
Macro expressions and side effects
#define SQUARE(x) ((x) * (x))Parentheses make SQUARE(2 + 3) expand with the intended grouping. They do not fix repeated evaluation: SQUARE(i++) expands to unsequenced modifications and is invalid. Prefer a typed function when evaluating a value once is important.
Conditional compilation
#ifdef DEBUG
fputs("debug build\n", stderr);
#endifThis fragment needs stdio.h. Compile with an appropriate definition such as -DDEBUG to include it. A preprocessor condition is resolved during translation, whereas an ordinary if makes a decision in the running program.
Useful inspection commands
With GCC, gcc -E main.c displays preprocessed output; gcc -c arithmetic.c creates an object file without linking. Keep generated output separate from your hand-written source. Include the header in the corresponding implementation file so declaration/definition mismatches can be diagnosed.
Exercises
- Add a subtract function to the module, updating both the header and source.
- Include arithmetic.h twice and confirm the include guard prevents repeated processing problems.
- Replace SQUARE with
int square(int x)and call it after a separate increment statement. - Explain why a declaration alone cannot provide an implementation to the linker.