C Language Preprocessor (Part 1): Macro Definitions and Applications

1. Essence of Macros

The preprocessor is the first step in the C language compilation process, performing text replacement on the source code before actual compilation. Macro definitions are a form of string replacement mechanism that does not involve type checking or semantic analysis.

Compilation Process:

Source Code → Preprocessing → Compilation → Assembly → Linking → Executable File

2. Defining Constants

Using <span>#define</span> to define symbolic constants is the most basic usage:

#define PI 3.14159
#define MAX_SIZE 1024
#define BUFFER_LEN (MAX_SIZE * 2)

// Usage example
double area = PI * r * r;
char buffer[MAX_SIZE];

Advantages:

  • Facilitates unified modification
  • Improves code readability
  • Compile-time constants, no runtime overhead

3. Macro Functions

3.1 Basic Usage

Macros can take parameters, achieving functionality similar to functions:

#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define SQUARE(x) ((x) * (x))

// Usage
int max_val = MAX(10, 20);        // Expands to ((10) > (20) ? (10) : (20))
int sq = SQUARE(5);                // Expands to ((5) * (5))

Note: Importance of Parentheses

// Incorrect example
#define SQUARE_BAD(x) x * x
int result = SQUARE_BAD(1 + 2);    // Expands to 1 + 2 * 1 + 2 = 5 (Incorrect!)

// Correct example
#define SQUARE(x) ((x) * (x))
int result = SQUARE(1 + 2);        // Expands to ((1 + 2) * (1 + 2)) = 9

3.2 Stringizing Operator <span>#</span>

<span>#</span> converts macro parameters to strings:

#define TO_STRING(x) #x
#define LOG(var) printf(#var " = %d\n", var)

// Usage example
const char* str = TO_STRING(Hello);  // Expands to "Hello"

int count = 100;
LOG(count);                          // Output: count = 100

3.3 Concatenation Operator <span>##</span>

<span>##</span> is used to concatenate two tokens:

#define CONCAT(a, b) a##b
#define MAKE_FUNC(name) void func_##name() { 
    printf("Function: " #name "\n"); 
}

// Usage example
int CONCAT(var, 123) = 456;          // Expands to int var123 = 456;

MAKE_FUNC(init)                      // Generates function func_init()
MAKE_FUNC(cleanup)                   // Generates function func_cleanup()

3.4 Variadic Macros

C99 introduced variadic macros, using <span>...</span> and <span>__VA_ARGS__</span>:

// Basic usage
#define LOG_INFO(...) printf("[INFO] " __VA_ARGS__)
#define DEBUG(fmt, ...) printf("[%s:%d] " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)

// Usage example
LOG_INFO("Server started on port %d\n", 8080);
// Expands to: printf("[INFO] " "Server started on port %d\n", 8080);

DEBUG("Value: %d, Status: %s", 42, "OK");
// Output: [main.c:10] Value: 42, Status: OK

GNU Extension: Named Variadic Parameters

#define LOG(level, fmt, args...) \
    printf("[%s] " fmt "\n", level, ##args)

LOG("ERROR", "Failed to open file");
LOG("WARNING", "Disk usage: %d%%", 95);

4. File Inclusion

4.1 <span>#include</span> Directive

#include <stdio.h>      // System header file, searched in system paths
#include "myheader.h"   // User header file, searched in current directory first

Differences:

  • <span><></span>: Searches only in system include paths (<span>/usr/include</span>, compiler specified paths)
  • <span>""</span>: Searches current directory first, then system paths

4.2 Preventing Multiple Inclusions

Method 1: Include Guard

// myheader.h
#ifndef MYHEADER_H
#define MYHEADER_H

// Header file content
void my_function();

#endif // MYHEADER_H

Method 2: <span>#pragma once</span>

// myheader.h
#pragma once

// Header file content
void my_function();

<span>#pragma once</span> is more concise but not standard C (though almost all compilers support it).

5. Command Line Definitions

Macros can be defined at compile time using the <span>-D</span> option:

gcc -DDEBUG -DVERSION=2 main.c -o app

Usage in code:

#ifdef DEBUG
    printf("Debug mode enabled\n");
#endif

#ifndef VERSION
    #define VERSION 1
#endif

printf("Version: %d\n", VERSION);

Practical Applications:

  • Debug switches
  • Version control
  • Platform difference handling
  • Feature trimming

6. Loop Unrolling Optimization

Macros can be used for manual loop unrolling to enhance performance:

// Regular loop
for (int i = 0; i < 4; i++) {
    sum += array[i];
}

// Using macro unrolling
#define UNROLL_4(op) \
    op(0); op(1); op(2); op(3);

#define ADD(i) sum += array[i]
UNROLL_4(ADD)

// After expansion, equivalent to:
// sum += array[0];
// sum += array[1];
// sum += array[2];
// sum += array[3];

Advantages: Reduces loop checks and jumps, improving CPU pipeline efficiency.

Disadvantages: Code bloat, requires careful consideration.

Summary

Macro definitions are powerful text processing tools in C language, and their proper use can:

  • ✅ Enhance code reusability
  • ✅ Enable compile-time calculations
  • ✅ Support conditional compilation
  • ⚠️ But be cautious of side effects and debugging difficulties

In the next article, we will introduce conditional compilation directives and other functionalities of the preprocessor.

Leave a Comment