Docs

README

Header Files and Include Guards in C

Table of Contents

  1. •Introduction
  2. •What are Header Files?
  3. •Creating Header Files
  4. •Include Guards
  5. •#pragma once
  6. •Header File Organization
  7. •Forward Declarations
  8. •Circular Dependencies
  9. •Best Practices
  10. •Common Patterns
  11. •System vs User Headers
  12. •Header File Examples

Introduction

Header files are a fundamental aspect of C programming that enable modular code organization, code reuse, and separate compilation. Understanding how to create and use header files effectively is essential for building maintainable C projects.

Why Use Header Files?

  1. •Code Organization: Separate interface from implementation
  2. •Reusability: Share declarations across multiple source files
  3. •Modularity: Build independent components
  4. •Maintainability: Centralize declarations for easy updates
  5. •Compilation Efficiency: Enable separate compilation
  6. •API Documentation: Document public interfaces

The Compilation Model

                    ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
                    │   header.h      │
                    │  (declarations) │
                    ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
                             │
              ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
              ā–¼              ā–¼              ā–¼
        ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”   ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”   ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
        │ main.c   │   │ file1.c  │   │ file2.c  │
        │ #include │   │ #include │   │ #include │
        ā””ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”˜   ā””ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”˜   ā””ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”˜
             │              │              │
             ā–¼              ā–¼              ā–¼
        ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”   ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”   ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
        │ main.o   │   │ file1.o  │   │ file2.o  │
        ā””ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”˜   ā””ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”˜   ā””ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”˜
             │              │              │
             ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
                            ā–¼
                     ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
                     │  program   │
                     │ (executable)│
                     ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

What are Header Files?

Header files (.h files) contain declarations that can be shared across multiple source files (.c files).

What Goes in a Header File

IncludeDon't Include
Function prototypesFunction definitions (body)
Type definitions (struct, enum, union)Variable definitions
Macro definitionsStatic variables
Extern variable declarationsImplementation details
Inline function definitionsInternal helper functions
Typedef declarations
Constant definitions

Example: math_utils.h

#ifndef MATH_UTILS_H
#define MATH_UTILS_H

// Type definitions
typedef struct {
    double x;
    double y;
} Point;

// Macro definitions
#define PI 3.14159265359
#define MAX(a, b) ((a) > (b) ? (a) : (b))

// Function prototypes
double distance(Point a, Point b);
double area_circle(double radius);
Point midpoint(Point a, Point b);

// Extern variable declaration
extern const double E;

#endif /* MATH_UTILS_H */

Example: math_utils.c

#include "math_utils.h"
#include <math.h>

// Variable definition
const double E = 2.71828182845;

// Function implementations
double distance(Point a, Point b) {
    double dx = b.x - a.x;
    double dy = b.y - a.y;
    return sqrt(dx * dx + dy * dy);
}

double area_circle(double radius) {
    return PI * radius * radius;
}

Point midpoint(Point a, Point b) {
    Point m = {(a.x + b.x) / 2, (a.y + b.y) / 2};
    return m;
}

Creating Header Files

Basic Structure

Every header file should have this structure:

#ifndef HEADER_NAME_H
#define HEADER_NAME_H

// Optional: C++ compatibility
#ifdef __cplusplus
extern "C" {
#endif

// Includes
#include <stddef.h>  // For size_t, etc.

// Macro definitions

// Type definitions

// Function prototypes

// Extern declarations

#ifdef __cplusplus
}
#endif

#endif /* HEADER_NAME_H */

Naming Conventions

// Header guard naming conventions
#ifndef PROJECT_MODULE_H        // Simple
#ifndef MYPROJECT_UTILS_H       // With project name
#ifndef COMPANY_PROJECT_FILE_H  // With namespace
#ifndef _FILE_H_               // With underscores (less common)

// File naming
utils.h              // Lowercase
string_utils.h       // Snake case
StringUtils.h        // Pascal case (less common in C)

Include Order

Recommended order of includes:

// 1. Corresponding header (for .c files)
#include "this_file.h"

// 2. System headers
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 3. Third-party library headers
#include <openssl/sha.h>
#include <curl/curl.h>

// 4. Project headers
#include "project/config.h"
#include "project/utils.h"

Include Guards

Include guards prevent a header from being included multiple times in the same compilation unit, which would cause errors.

The Multiple Inclusion Problem

// Without include guards:

// a.h
struct Point { int x; int y; };

// b.h
#include "a.h"
void use_point(struct Point p);

// main.c
#include "a.h"  // Point defined here
#include "b.h"  // a.h included again - ERROR: Point redefined!

Traditional Include Guards

#ifndef HEADER_NAME_H
#define HEADER_NAME_H

// Header content here

#endif /* HEADER_NAME_H */

How Include Guards Work

// First inclusion:
// 1. #ifndef UTILS_H evaluates to true (UTILS_H not defined)
// 2. #define UTILS_H creates the macro
// 3. Header content is included
// 4. #endif ends the conditional

// Second inclusion:
// 1. #ifndef UTILS_H evaluates to false (UTILS_H now defined)
// 2. Everything until #endif is skipped
// 3. Header content is NOT included again

Include Guard Naming Strategies

// Method 1: Simple filename
#ifndef UTILS_H
#define UTILS_H

// Method 2: Full path simulation
#ifndef PROJECT_SRC_UTILS_H
#define PROJECT_SRC_UTILS_H

// Method 3: UUID-based (guaranteed unique)
#ifndef UTILS_H_A7F3B2C1_9D4E_48F6_BC5A_1234567890AB
#define UTILS_H_A7F3B2C1_9D4E_48F6_BC5A_1234567890AB

// Method 4: Date/time based
#ifndef UTILS_H_20240115_143022
#define UTILS_H_20240115_143022

#pragma once

#pragma once is a non-standard but widely supported alternative to include guards.

Syntax

#pragma once

// Header content here
// No #endif needed

Comparison

FeatureInclude Guards#pragma once
Standard CYesNo (extension)
PortabilityUniversalMost modern compilers
TypingMore verboseSimpler
Unique namesMust ensureAutomatic
File detectionText-basedFile-based
SymlinksWorksMay have issues

Compiler Support

// Supported by:
// - GCC (all versions)
// - Clang (all versions)
// - MSVC (all versions)
// - Intel C Compiler
// - Most other modern compilers

// Safe usage pattern (belt and suspenders):
#ifndef HEADER_H
#define HEADER_H
#pragma once

// Content here

#endif

When to Use Each

// Use include guards when:
// - Maximum portability is required
// - Working with old compilers
// - Building for embedded systems
// - Header files may be accessed via symlinks

// Use #pragma once when:
// - Modern compilers only
// - Simpler code is preferred
// - Large projects (faster compilation)
// - No symlink concerns

Header File Organization

Module-Based Organization

project/
ā”œā”€ā”€ include/              # Public headers
│   ā”œā”€ā”€ project/
│   │   ā”œā”€ā”€ config.h
│   │   ā”œā”€ā”€ types.h
│   │   └── api.h
│   └── project.h         # Main include
ā”œā”€ā”€ src/                  # Source files
│   ā”œā”€ā”€ internal.h        # Private headers
│   ā”œā”€ā”€ config.c
│   └── api.c
└── tests/
    └── test_api.c

Single Responsibility

Each header should have a clear, single purpose:

// types.h - Type definitions only
#ifndef TYPES_H
#define TYPES_H

typedef struct {
    int id;
    char name[64];
} User;

typedef enum {
    STATUS_OK,
    STATUS_ERROR,
    STATUS_PENDING
} Status;

#endif

// operations.h - Function declarations only
#ifndef OPERATIONS_H
#define OPERATIONS_H

#include "types.h"

User* create_user(int id, const char *name);
void delete_user(User *user);
Status process_user(User *user);

#endif

Layered Dependencies

// Layer 1: Basic types (no dependencies)
// base_types.h
#ifndef BASE_TYPES_H
#define BASE_TYPES_H
typedef unsigned char byte;
typedef unsigned int uint;
#endif

// Layer 2: Depends on Layer 1
// data_types.h
#ifndef DATA_TYPES_H
#define DATA_TYPES_H
#include "base_types.h"
typedef struct { byte data[16]; } UUID;
#endif

// Layer 3: Depends on Layers 1-2
// operations.h
#ifndef OPERATIONS_H
#define OPERATIONS_H
#include "data_types.h"
UUID generate_uuid(void);
#endif

Forward Declarations

Forward declarations allow you to declare a type without fully defining it, reducing header dependencies.

When to Use Forward Declarations

// utils.h - Avoid including large headers
#ifndef UTILS_H
#define UTILS_H

// Forward declaration instead of #include "database.h"
struct Database;

// Function that only uses pointer to Database
void log_db_status(struct Database *db);

#endif

Forward Declaration Syntax

// Structure forward declaration
struct StructName;

// Typedef with forward declaration
typedef struct StructName StructName;

// Enum forward declaration (C11+)
enum EnumName : int;  // Only with underlying type

// Union forward declaration
union UnionName;

Complete Example

// node.h
#ifndef NODE_H
#define NODE_H

// Forward declaration - Tree is defined elsewhere
struct Tree;

typedef struct Node {
    int value;
    struct Node *left;
    struct Node *right;
    struct Tree *owner;  // Only need pointer, not full definition
} Node;

Node* node_create(int value);
void node_destroy(Node *node);

#endif

// tree.h
#ifndef TREE_H
#define TREE_H

#include "node.h"  // Need full definition to use Node

typedef struct Tree {
    Node *root;
    int size;
} Tree;

Tree* tree_create(void);
void tree_insert(Tree *tree, int value);

#endif

Opaque Pointers (Pimpl Pattern)

// public_api.h - Public header
#ifndef PUBLIC_API_H
#define PUBLIC_API_H

// Opaque type - implementation hidden
typedef struct Context Context;

// Public API
Context* context_create(void);
void context_destroy(Context *ctx);
int context_process(Context *ctx, const char *data);

#endif

// private_impl.c - Implementation
#include "public_api.h"
#include <stdlib.h>
#include <string.h>

// Private definition
struct Context {
    char *buffer;
    size_t buffer_size;
    int state;
    // ... more private fields
};

Context* context_create(void) {
    Context *ctx = malloc(sizeof(Context));
    if (ctx) {
        ctx->buffer = NULL;
        ctx->buffer_size = 0;
        ctx->state = 0;
    }
    return ctx;
}

// ... other implementations

Circular Dependencies

Circular dependencies occur when two headers depend on each other.

The Problem

// a.h
#ifndef A_H
#define A_H
#include "b.h"  // Needs B
typedef struct { B *b; } A;
#endif

// b.h
#ifndef B_H
#define B_H
#include "a.h"  // Needs A - CIRCULAR!
typedef struct { A *a; } B;
#endif

Solution: Forward Declarations

// a.h
#ifndef A_H
#define A_H

struct B;  // Forward declaration

typedef struct A {
    struct B *b;
} A;

void a_set_b(A *a, struct B *b);

#endif

// b.h
#ifndef B_H
#define B_H

struct A;  // Forward declaration

typedef struct B {
    struct A *a;
} B;

void b_set_a(B *b, struct A *a);

#endif

Solution: Common Types Header

// types.h
#ifndef TYPES_H
#define TYPES_H

typedef struct A A;
typedef struct B B;

struct A {
    B *b;
    int value;
};

struct B {
    A *a;
    char name[32];
};

#endif

// a_ops.h
#ifndef A_OPS_H
#define A_OPS_H
#include "types.h"
A* a_create(int value);
#endif

// b_ops.h
#ifndef B_OPS_H
#define B_OPS_H
#include "types.h"
B* b_create(const char *name);
#endif

Best Practices

1. Self-Contained Headers

Every header should compile on its own:

// Good: Header includes its dependencies
#ifndef CONFIG_H
#define CONFIG_H

#include <stddef.h>  // For size_t

typedef struct {
    size_t buffer_size;
    // ...
} Config;

#endif

// Bad: Relies on includer to provide dependencies
#ifndef CONFIG_H
#define CONFIG_H

// Assumes size_t is defined somewhere - might fail!
typedef struct {
    size_t buffer_size;
} Config;

#endif

2. Minimize Dependencies

// Bad: Includes everything
#ifndef UTILS_H
#define UTILS_H

#include "database.h"
#include "network.h"
#include "graphics.h"
#include "audio.h"

void log_message(const char *msg);

#endif

// Good: Only include what's needed
#ifndef UTILS_H
#define UTILS_H

void log_message(const char *msg);

#endif

3. Use Forward Declarations

// Prefer this:
struct HeavyObject;
void process(struct HeavyObject *obj);

// Over this:
#include "heavy_object.h"  // Large header with many dependencies
void process(HeavyObject *obj);

4. Document Public Headers

#ifndef CALCULATOR_H
#define CALCULATOR_H

/**
 * @file calculator.h
 * @brief Mathematical calculator functions
 * @author Your Name
 * @date 2024-01-15
 */

/**
 * @brief Adds two integers
 * @param a First operand
 * @param b Second operand
 * @return Sum of a and b
 */
int add(int a, int b);

/**
 * @brief Divides two integers
 * @param dividend The number to be divided
 * @param divisor The number to divide by
 * @param result Pointer to store the result
 * @return 0 on success, -1 if divisor is zero
 */
int divide(int dividend, int divisor, int *result);

#endif /* CALCULATOR_H */

5. Use Consistent Naming

// For project "mylib"
// Files: mylib_string.h, mylib_math.h, mylib_io.h

// Guards: MYLIB_STRING_H, MYLIB_MATH_H, MYLIB_IO_H

// Types: MyLib_String, MyLib_Vector

// Functions: mylib_string_create(), mylib_math_sqrt()

6. Separate Interface from Implementation

// string_utils.h - PUBLIC INTERFACE
#ifndef STRING_UTILS_H
#define STRING_UTILS_H

char* string_duplicate(const char *str);
char* string_concat(const char *a, const char *b);
int string_compare(const char *a, const char *b);

#endif

// string_utils_internal.h - PRIVATE (in src directory)
#ifndef STRING_UTILS_INTERNAL_H
#define STRING_UTILS_INTERNAL_H

#include "string_utils.h"

// Internal helpers not exposed to users
static inline int is_empty(const char *str) {
    return str == NULL || *str == '\0';
}

#endif

Common Patterns

Pattern 1: Configuration Header

// config.h
#ifndef CONFIG_H
#define CONFIG_H

// Build configuration
#ifndef DEBUG
    #define DEBUG 0
#endif

// Feature flags
#define FEATURE_LOGGING 1
#define FEATURE_NETWORKING 1
#define FEATURE_GUI 0

// Platform detection
#if defined(_WIN32)
    #define PLATFORM_WINDOWS 1
#elif defined(__linux__)
    #define PLATFORM_LINUX 1
#elif defined(__APPLE__)
    #define PLATFORM_MACOS 1
#endif

// Limits and defaults
#define MAX_BUFFER_SIZE 4096
#define DEFAULT_TIMEOUT 30

#endif /* CONFIG_H */

Pattern 2: Type Definitions Header

// types.h
#ifndef TYPES_H
#define TYPES_H

#include <stdint.h>
#include <stdbool.h>

// Basic type aliases
typedef uint8_t  u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;

typedef int8_t  i8;
typedef int16_t i16;
typedef int32_t i32;
typedef int64_t i64;

typedef float  f32;
typedef double f64;

// Common structures
typedef struct {
    f32 x, y;
} Vec2;

typedef struct {
    f32 x, y, z;
} Vec3;

// Result type
typedef struct {
    bool success;
    int error_code;
    char message[256];
} Result;

#endif /* TYPES_H */

Pattern 3: C++ Compatibility

// api.h
#ifndef API_H
#define API_H

#ifdef __cplusplus
extern "C" {
#endif

// API declarations
int api_init(void);
void api_cleanup(void);
int api_process(const char *input, char *output, size_t output_size);

#ifdef __cplusplus
}
#endif

#endif /* API_H */

Pattern 4: Version Information

// version.h
#ifndef VERSION_H
#define VERSION_H

#define VERSION_MAJOR 1
#define VERSION_MINOR 2
#define VERSION_PATCH 3

#define VERSION_STRING "1.2.3"

#define VERSION_NUMBER ((VERSION_MAJOR << 16) | (VERSION_MINOR << 8) | VERSION_PATCH)

// Version checking macros
#define VERSION_AT_LEAST(major, minor, patch) \
    (VERSION_NUMBER >= ((major << 16) | (minor << 8) | patch))

const char* get_version_string(void);
int get_version_number(void);

#endif /* VERSION_H */

Pattern 5: Inline Functions in Headers

// inline_utils.h
#ifndef INLINE_UTILS_H
#define INLINE_UTILS_H

#include <stdlib.h>

// C99+ inline functions
static inline int max_int(int a, int b) {
    return (a > b) ? a : b;
}

static inline int min_int(int a, int b) {
    return (a < b) ? a : b;
}

static inline void* safe_malloc(size_t size) {
    void *ptr = malloc(size);
    if (ptr == NULL && size > 0) {
        fprintf(stderr, "Memory allocation failed\n");
        exit(EXIT_FAILURE);
    }
    return ptr;
}

static inline int clamp_int(int value, int min_val, int max_val) {
    if (value < min_val) return min_val;
    if (value > max_val) return max_val;
    return value;
}

#endif /* INLINE_UTILS_H */

System vs User Headers

System Headers

System headers are part of the C standard library or operating system:

#include <stdio.h>    // Standard I/O
#include <stdlib.h>   // Standard library
#include <string.h>   // String functions
#include <math.h>     // Math functions
#include <time.h>     // Time functions
#include <stdint.h>   // Fixed-width integers
#include <stdbool.h>  // Boolean type

User Headers

User headers are project-specific:

#include "myheader.h"        // Same directory
#include "utils/helper.h"    // Relative path
#include "project/config.h"  // Project path

Search Order

SyntaxSearch Order
<header.h>System directories only
"header.h"Current directory, then system directories

Setting Include Paths

# GCC/Clang: Add include directory
gcc -I/path/to/includes -I./include main.c

# Multiple projects
gcc -I./project1/include -I./project2/include main.c

# Common structure
project/
ā”œā”€ā”€ include/    # -I./include
ā”œā”€ā”€ src/
└── main.c

Header File Examples

Complete Example: Stack Module

// stack.h
#ifndef STACK_H
#define STACK_H

#include <stddef.h>
#include <stdbool.h>

/**
 * @file stack.h
 * @brief Generic stack data structure
 */

// Forward declaration for opaque pointer
typedef struct Stack Stack;

/**
 * @brief Create a new stack
 * @param element_size Size of each element in bytes
 * @param initial_capacity Initial capacity (0 for default)
 * @return Pointer to new stack, or NULL on failure
 */
Stack* stack_create(size_t element_size, size_t initial_capacity);

/**
 * @brief Destroy a stack and free all memory
 * @param stack Pointer to stack to destroy
 */
void stack_destroy(Stack *stack);

/**
 * @brief Push an element onto the stack
 * @param stack Pointer to stack
 * @param element Pointer to element to push
 * @return true on success, false on failure
 */
bool stack_push(Stack *stack, const void *element);

/**
 * @brief Pop an element from the stack
 * @param stack Pointer to stack
 * @param element Pointer to store popped element
 * @return true on success, false if stack is empty
 */
bool stack_pop(Stack *stack, void *element);

/**
 * @brief Peek at the top element without removing it
 * @param stack Pointer to stack
 * @param element Pointer to store peeked element
 * @return true on success, false if stack is empty
 */
bool stack_peek(const Stack *stack, void *element);

/**
 * @brief Check if stack is empty
 * @param stack Pointer to stack
 * @return true if empty, false otherwise
 */
bool stack_is_empty(const Stack *stack);

/**
 * @brief Get number of elements in stack
 * @param stack Pointer to stack
 * @return Number of elements
 */
size_t stack_size(const Stack *stack);

/**
 * @brief Clear all elements from stack
 * @param stack Pointer to stack
 */
void stack_clear(Stack *stack);

#endif /* STACK_H */
// stack.c
#include "stack.h"
#include <stdlib.h>
#include <string.h>

#define DEFAULT_CAPACITY 16

struct Stack {
    void *data;
    size_t element_size;
    size_t capacity;
    size_t size;
};

Stack* stack_create(size_t element_size, size_t initial_capacity) {
    if (element_size == 0) return NULL;

    Stack *stack = malloc(sizeof(Stack));
    if (stack == NULL) return NULL;

    size_t cap = (initial_capacity > 0) ? initial_capacity : DEFAULT_CAPACITY;

    stack->data = malloc(element_size * cap);
    if (stack->data == NULL) {
        free(stack);
        return NULL;
    }

    stack->element_size = element_size;
    stack->capacity = cap;
    stack->size = 0;

    return stack;
}

void stack_destroy(Stack *stack) {
    if (stack != NULL) {
        free(stack->data);
        free(stack);
    }
}

static bool stack_grow(Stack *stack) {
    size_t new_capacity = stack->capacity * 2;
    void *new_data = realloc(stack->data, stack->element_size * new_capacity);
    if (new_data == NULL) return false;

    stack->data = new_data;
    stack->capacity = new_capacity;
    return true;
}

bool stack_push(Stack *stack, const void *element) {
    if (stack == NULL || element == NULL) return false;

    if (stack->size >= stack->capacity) {
        if (!stack_grow(stack)) return false;
    }

    void *dest = (char *)stack->data + (stack->size * stack->element_size);
    memcpy(dest, element, stack->element_size);
    stack->size++;

    return true;
}

bool stack_pop(Stack *stack, void *element) {
    if (stack == NULL || stack->size == 0) return false;

    stack->size--;
    void *src = (char *)stack->data + (stack->size * stack->element_size);

    if (element != NULL) {
        memcpy(element, src, stack->element_size);
    }

    return true;
}

bool stack_peek(const Stack *stack, void *element) {
    if (stack == NULL || stack->size == 0 || element == NULL) return false;

    void *src = (char *)stack->data + ((stack->size - 1) * stack->element_size);
    memcpy(element, src, stack->element_size);

    return true;
}

bool stack_is_empty(const Stack *stack) {
    return (stack == NULL || stack->size == 0);
}

size_t stack_size(const Stack *stack) {
    return (stack != NULL) ? stack->size : 0;
}

void stack_clear(Stack *stack) {
    if (stack != NULL) {
        stack->size = 0;
    }
}

Summary

Key Takeaways

  1. •Header files separate interface from implementation
  2. •Include guards prevent multiple inclusion
  3. •#pragma once is simpler but less portable
  4. •Forward declarations reduce dependencies
  5. •Self-contained headers include their own dependencies
  6. •Minimize includes to reduce compilation time

Header File Checklist

  • • Include guard or #pragma once present
  • • Self-contained (compiles on its own)
  • • Only necessary includes
  • • Forward declarations where possible
  • • C++ compatibility if needed
  • • Proper documentation
  • • Consistent naming conventions
  • • No definitions (only declarations)

Quick Reference

// Minimal header template
#ifndef PROJECT_MODULE_H
#define PROJECT_MODULE_H

#ifdef __cplusplus
extern "C" {
#endif

// Declarations here

#ifdef __cplusplus
}
#endif

#endif /* PROJECT_MODULE_H */

This document is part of the C Programming Language course. For practical exercises, see the accompanying exercises.c file.

README - C Programming Tutorial | DeepML