Header File Organization
Master professional header file practices: include guards, forward declarations, separation of interface/implementation, avoiding circular dependencies, and organizing large C projects for maintainability and fast compilation.
Header File Purpose and Structure
Header files (.h) declare interfaces - what's available to other modules. Implementation files (.c) define how it works. This separation enables modularity, recompilation efficiency, and information hiding. Good headers are self-contained, minimal, and document the public API.
/* mymodule.h - Interface */
#ifndef MYMODULE_H
#define MYMODULE_H
/* Includes needed by declarations */
#include <stddef.h>
/* Opaque type (hide implementation) */
typedef struct MyModule MyModule;
/* Public API */
MyModule* mymodule_create(size_t size);
void mymodule_destroy(MyModule *mod);
int mymodule_process(MyModule *mod, const char *data);
#endif /* MYMODULE_H */
/* mymodule.c - Implementation */
#include "mymodule.h"
#include <stdlib.h>
#include <string.h>
/* Complete type definition (private) */
struct MyModule {
char *buffer;
size_t size;
int state;
};
/* Implementation */
MyModule* mymodule_create(size_t size) {
MyModule *mod = malloc(sizeof(MyModule));
if (mod == NULL) {
return NULL;
}
mod->buffer = malloc(size);
if (mod->buffer == NULL) {
free(mod);
return NULL;
}
mod->size = size;
mod->state = 0;
return mod;
}
void mymodule_destroy(MyModule *mod) {
if (mod != NULL) {
free(mod->buffer);
free(mod);
}
}
int mymodule_process(MyModule *mod, const char *data) {
if (mod == NULL || data == NULL) {
return -1;
}
/* Implementation details */
strncpy(mod->buffer, data, mod->size - 1);
mod->buffer[mod->size - 1] = '\0';
mod->state = 1;
return 0;
}
/* What goes in headers */
/*
DO include:
- Type declarations
- Function prototypes
- Macro definitions (if public)
- Extern variables (sparingly)
- Inline functions (small only)
DON'T include:
- Function implementations
- Static variables
- Private helpers
- Implementation details
*/Include Guards
Include guards prevent multiple inclusion of the same header. Without them, redefinition errors occur. Use #ifndef/#define/#endif pattern or #pragma once. Guards must be unique across project. Follow naming convention based on file path.
/* Traditional include guard */
#ifndef PROJECT_MODULE_HEADER_H
#define PROJECT_MODULE_HEADER_H
/* Header contents */
#endif /* PROJECT_MODULE_HEADER_H */
/* Naming conventions */
/* File: myproject/network/socket.h */
#ifndef MYPROJECT_NETWORK_SOCKET_H
#define MYPROJECT_NETWORK_SOCKET_H
/* ... */
#endif
/* Or with underscores */
#ifndef MYPROJECT_NETWORK_SOCKET_H_
#define MYPROJECT_NETWORK_SOCKET_H_
/* ... */
#endif
/* #pragma once (modern alternative) */
#pragma once
/* Header contents */
/* Comparison */
/*
Traditional (#ifndef):
+ Standard C
+ Works everywhere
- More verbose
- Name collisions possible
#pragma once:
+ Cleaner
+ No naming issues
- Non-standard (but widely supported)
- May not work with symlinks
*/
/* Why include guards matter */
/* Without guards - BAD */
/* file1.h */
struct Data {
int value;
};
/* file2.h */
#include "file1.h"
/* main.c */
#include "file1.h"
#include "file2.h"
/* Error: struct Data redefined! */
/* With guards - GOOD */
/* file1.h */
#ifndef FILE1_H
#define FILE1_H
struct Data {
int value;
};
#endif
/* file2.h */
#ifndef FILE2_H
#define FILE2_H
#include "file1.h"
#endif
/* main.c */
#include "file1.h" /* struct Data defined */
#include "file2.h" /* file1.h guard prevents redefinition */
/* OK! */
/* Guard placement */
/* CORRECT */
#ifndef HEADER_H
#define HEADER_H
#include <stdio.h>
/* All contents protected */
#endif
/* WRONG: Includes outside guard */
#include <stdio.h> /* Outside guard */
#ifndef HEADER_H
#define HEADER_H
/* Contents */
#endif
/* Comments in guards */
/* Start of header */
#ifndef MY_HEADER_H
#define MY_HEADER_H
/* ... lots of code ... */
#endif /* MY_HEADER_H - helps match start */
/* Multiple guard styles in project */
/* Pick one convention and stick to it! */
/* Style 1: Path-based */
/* src/network/tcp.h */
#ifndef SRC_NETWORK_TCP_H
#define SRC_NETWORK_TCP_H
/* Style 2: Project prefix */
/* tcp.h in "myproject" */
#ifndef MYPROJECT_TCP_H
#define MYPROJECT_TCP_H
/* Style 3: UUID-based (rare) */
#ifndef H_F3A2B1C4_D5E6_7890_ABCD_EF1234567890
#define H_F3A2B1C4_D5E6_7890_ABCD_EF1234567890
/* Conditional content in headers */
#ifndef CONFIG_H
#define CONFIG_H
/* Platform-specific in header */
#ifdef _WIN32
#define API_EXPORT __declspec(dllexport)
#else
#define API_EXPORT
#endif
API_EXPORT void my_function(void);
#endifForward Declarations
Forward declarations announce types without defining them. Reduces header dependencies, speeds compilation, breaks circular dependencies. Use pointers to incomplete types in headers. Define complete types in implementation files.
/* Without forward declaration - BAD */
/* a.h */
#include "b.h" /* Need complete B */
typedef struct A {
B *b_ptr;
} A;
/* b.h */
#include "a.h" /* Need complete A */
typedef struct B {
A *a_ptr;
} B;
/* Circular dependency! Won't compile */
/* With forward declaration - GOOD */
/* a.h */
typedef struct B B; /* Forward declaration */
typedef struct A {
B *b_ptr; /* Pointer to incomplete type OK */
} A;
/* b.h */
typedef struct A A; /* Forward declaration */
typedef struct B {
A *a_ptr;
} B;
/* Both compile fine! */
/* Minimizing includes with forward declarations */
/* Bad: Full include */
/* widget.h */
#include "config.h" /* Just for Config* pointer */
typedef struct Widget {
Config *config; /* Only use pointer */
} Widget;
/* Good: Forward declaration */
/* widget.h */
typedef struct Config Config; /* Forward */
typedef struct Widget {
Config *config;
} Widget;
/* widget.c - need complete type here */
#include "widget.h"
#include "config.h" /* Full include only in .c */
void widget_init(Widget *w) {
w->config->value = 10; /* Now can access members */
}
/* When forward declaration works */
/*
Can use incomplete type for:
- Pointers
- Function parameters
- Return types
Cannot use for:
- Member by value (needs size)
- Array member (needs size)
- sizeof()
- Dereferencing
*/
/* Example */
typedef struct Node Node; /* Forward */
/* OK: Pointer */
Node* create_node(void);
/* OK: Function parameter */
void process_node(Node *n);
/* NOT OK: Member by value */
typedef struct List {
Node head; /* Error: incomplete type */
} List;
/* OK: Pointer member */
typedef struct List {
Node *head; /* OK */
} List;
/* Opaque types (pimpl idiom) */
/* Public header - minimal */
/* database.h */
typedef struct Database Database;
Database* db_create(const char *path);
void db_destroy(Database *db);
int db_query(Database *db, const char *sql);
/* Private implementation */
/* database.c */
#include "database.h"
#include <sqlite3.h>
struct Database {
sqlite3 *handle;
char *error_msg;
int connection_count;
};
/* Advantages:
- Hides implementation details
- Reduces recompilation
- Enables ABI stability
- Prevents misuse
*/
/* Multiple forward declarations */
/* common.h */
typedef struct User User;
typedef struct Session Session;
typedef struct Request Request;
typedef struct Response Response;
/* Now can use in headers without includes */
/* Forward declaration of functions */
/* main.h */
void helper_function(void); /* Forward */
void main_function(void);
/* main.c */
void helper_function(void) {
/* Implementation */
}
void main_function(void) {
helper_function(); /* OK */
}
/* Forward declarations and typedefs */
/* Method 1: Separate forward and typedef */
struct Node; /* Forward */
typedef struct Node Node; /* Typedef */
/* Method 2: Combined */
typedef struct Node Node; /* Forward + typedef */
/* Method 3: In definition */
typedef struct Node {
int data;
Node *next;
} Node;Header Dependencies and Organization
Organize headers to minimize dependencies. Group related declarations. Order includes consistently. Avoid transitive dependencies - include only what you directly use. Use internal headers for module-private declarations.
/* Project structure */
/*
myproject/
├── include/ # Public headers
│ ├── myproject/
│ │ ├── api.h # Main API
│ │ ├── types.h # Common types
│ │ └── utils.h
│ └── myproject.h # Umbrella header
├── src/ # Implementation
│ ├── internal/ # Private headers
│ │ ├── impl.h
│ │ └── private.h
│ ├── module1.c
│ └── module2.c
└── tests/
*/
/* Public header dependencies */
/* Base header - no dependencies */
/* types.h */
#ifndef MYPROJECT_TYPES_H
#define MYPROJECT_TYPES_H
#include <stddef.h>
#include <stdint.h>
typedef uint32_t ObjectID;
typedef int ErrorCode;
#endif
/* Mid-level header - depends on types */
/* api.h */
#ifndef MYPROJECT_API_H
#define MYPROJECT_API_H
#include "myproject/types.h"
ErrorCode init(void);
ObjectID create_object(size_t size);
void destroy_object(ObjectID id);
#endif
/* High-level header - depends on others */
/* utils.h */
#ifndef MYPROJECT_UTILS_H
#define MYPROJECT_UTILS_H
#include "myproject/types.h"
#include "myproject/api.h"
ErrorCode validate_object(ObjectID id);
#endif
/* Umbrella header - includes everything */
/* myproject.h */
#ifndef MYPROJECT_H
#define MYPROJECT_H
#include "myproject/types.h"
#include "myproject/api.h"
#include "myproject/utils.h"
#endif
/* Include order in .c files */
/* module.c */
/* 1. Own header first (catches missing includes) */
#include "module.h"
/* 2. Project internal headers */
#include "internal/impl.h"
#include "internal/private.h"
/* 3. Other project headers */
#include "myproject/types.h"
#include "myproject/utils.h"
/* 4. Standard library */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* 5. System headers */
#include <unistd.h>
#include <sys/types.h>
/* Internal headers */
/* internal/impl.h */
#ifndef INTERNAL_IMPL_H
#define INTERNAL_IMPL_H
/* Not for public use */
#include "../include/myproject/types.h"
/* Private functions */
void internal_helper(void);
int internal_validate(ObjectID id);
/* Private structures */
struct InternalData {
int field1;
int field2;
};
#endif
/* Dependency management */
/* Minimize includes in headers */
/* BAD */
/* widget.h */
#include <stdio.h> /* Don't need */
#include <stdlib.h> /* Don't need */
#include <string.h> /* Don't need */
#include "config.h" /* Only need forward decl */
#include "utils.h" /* Don't need */
/* GOOD */
/* widget.h */
#include <stddef.h> /* Need for size_t */
typedef struct Config Config; /* Forward */
/* Precompiled headers (for large projects) */
/* stdafx.h or pch.h */
#ifndef PCH_H
#define PCH_H
/* Stable, rarely-changed headers */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
/* Platform headers */
#ifdef _WIN32
#include <windows.h>
#endif
#endif
/* Compile: gcc -x c-header pch.h -o pch.h.gch */
/* Header self-sufficiency test */
/* Every header should compile alone */
/* test_headers.sh */
/* for file in include/**/*.h; do
gcc -c -x c "$file" -o /dev/null || echo "FAIL: $file"
done */
/* Header dependency graph */
/*
types.h
↓
api.h
↓
utils.h
↓
myproject.h (umbrella)
*/
/* Avoid circular dependencies */
/* If A needs B and B needs A: */
/* Solution 1: Move common code to C */
/* common.h */
/* common code */
/* a.h */
#include "common.h"
/* b.h */
#include "common.h"
/* Solution 2: Forward declarations */
/* a.h */
typedef struct B B; /* Forward */
/* b.h */
typedef struct A A; /* Forward */
/* Solution 3: Restructure (best) */
/* Maybe A and B should be in same module */Best Practices
Professional header management requires discipline: guards always, includes minimized, dependencies clear, self-contained headers, documented APIs. These practices scale from small projects to large codebases.
/* Complete example of professional header */
/* module.h */
#ifndef PROJECT_MODULE_H
#define PROJECT_MODULE_H
/* Documentation */
/**
* @file module.h
* @brief Module for data processing
* @author Your Name
*/
/* Required includes (minimal) */
#include <stddef.h> /* For size_t */
#include <stdint.h> /* For uint32_t */
/* Forward declarations */
typedef struct Config Config;
/* Constants */
#define MODULE_VERSION 1
#define MODULE_MAX_SIZE 1024
/* Typedefs */
typedef uint32_t ModuleID;
/* Enums */
typedef enum {
MODULE_SUCCESS = 0,
MODULE_ERROR_NOMEM = -1,
MODULE_ERROR_INVALID = -2
} ModuleError;
/* Opaque type */
typedef struct Module Module;
/* Public API with documentation */
/**
* Creates a new module instance
* @param size Initial size
* @return Module pointer or NULL on error
*/
Module* module_create(size_t size);
/**
* Destroys module and frees resources
* @param mod Module to destroy (can be NULL)
*/
void module_destroy(Module *mod);
/**
* Processes data
* @param mod Module instance
* @param data Input data
* @param size Data size
* @return MODULE_SUCCESS or error code
*/
ModuleError module_process(Module *mod, const void *data, size_t size);
/* Platform-specific exports */
#ifdef _WIN32
#ifdef MODULE_EXPORTS
#define MODULE_API __declspec(dllexport)
#else
#define MODULE_API __declspec(dllimport)
#endif
#else
#define MODULE_API
#endif
/* Deprecation warnings */
#ifdef __GNUC__
#define DEPRECATED __attribute__((deprecated))
#else
#define DEPRECATED
#endif
DEPRECATED void old_function(void);
/* Version checking */
#define MODULE_VERSION_MAJOR 1
#define MODULE_VERSION_MINOR 2
#define MODULE_VERSION_PATCH 3
#define MODULE_VERSION_CHECK(major, minor) \
((MODULE_VERSION_MAJOR > (major)) || \
(MODULE_VERSION_MAJOR == (major) && MODULE_VERSION_MINOR >= (minor)))
/* C++ compatibility */
#ifdef __cplusplus
extern "C" {
#endif
/* Declarations here */
#ifdef __cplusplus
}
#endif
#endif /* PROJECT_MODULE_H */
/* Common mistakes to avoid */
/* 1. Using declarations in headers */
/* BAD */
int global_var = 0; /* Definition in header */
/* GOOD */
extern int global_var; /* Declaration */
/* Define in .c file: int global_var = 0; */
/* 2. Static in headers (creates copy per file) */
/* BAD */
static int helper(void) { return 42; }
/* GOOD: Inline or move to .c */
static inline int helper(void) { return 42; }
/* 3. Including unnecessary headers */
/* BAD */
#include "everything.h" /* Kitchen sink */
/* GOOD */
#include <stddef.h> /* Only what's needed */
/* 4. Missing include guards */
/* BAD */
/* No guard */
struct Data { int x; };
/* GOOD */
#ifndef DATA_H
#define DATA_H
struct Data { int x; };
#endif
/* 5. Polluting namespace */
/* BAD */
#define SIZE 100 /* Too generic */
/* GOOD */
#define MYMODULE_DEFAULT_SIZE 100
/* Checklist for headers */
/*
✓ Include guard (or #pragma once)
✓ Minimal includes
✓ Forward declarations where possible
✓ Documentation comments
✓ No definitions (only declarations)
✓ Self-contained (compiles alone)
✓ Consistent naming
✓ Platform handling
✓ C++ compatibility (if needed)
*/Summary & What's Next
Key Takeaways:
- ✅ Headers declare interface, .c files implement
- ✅ Always use include guards or #pragma once
- ✅ Forward declarations reduce dependencies
- ✅ Minimize includes in headers
- ✅ Use opaque types to hide implementation
- ✅ Order includes consistently
- ✅ Keep headers self-contained
- ✅ Document public APIs thoroughly