C

C Fundamentals

18 lessons

Progress0%
1. Introduction to C
1What is C?
2. Variables and Data Types
1Data Types in C
3. Control Flow
ConditionalsLoops
4. Functions
Defining FunctionsRecursion
5. Arrays and Pointers
Arrays and StringsPointers
6. Memory Management
Dynamic MemoryStructs and Files
7. Preprocessor & Macros
Preprocessor DirectivesMacros & Inline Functions
8. Bitwise Operations
Bitwise OperatorsBit Flags & Masking
9. Enums, Unions & typedef
Enums & typedefUnions & Complex Types
10. Multi-file Programs
Header Files & Compilation UnitsLinkage, Storage Classes & Make
All Tutorials
CPreprocessor & Macros
Lesson 12 of 18 min
Chapter 7 · Lesson 2

Macros & Inline Functions

Macros & Inline Functions

Macros are one of the most powerful — and most dangerous — features of the C preprocessor. Understanding both their capabilities and their pitfalls is crucial for reading and writing real-world C code.

Object-like Macros

The simplest kind replaces a name with a value. By convention macro names are written in ALL_CAPS:

c
#define PI        3.14159265358979323846
#define MAX_NODES 1024
#define NEWLINE   '\n'

Unlike const variables, object-like macros have no type, no storage, and no scope — they are pure text substitution. The compiler never sees the macro name, only the replacement text.

Function-like Macros

A macro can take arguments, mimicking a function:

c
#define SQUARE(x)  x * x   /* DANGEROUS — do not use */
#define SQUARE(x)  ((x) * (x))  /* safer */

The Two Great Macro Pitfalls

Operator precedence: SQUARE(1 + 2) with the unsafe definition expands to 1 + 2 * 1 + 2 = 5, not 9. Always wrap both the entire expression and each parameter in parentheses.

Side effects: SQUARE(i++) expands to ((i++) * (i++)) — i is incremented twice. Never pass expressions with side effects to function-like macros.

Stringification (#)

Placing # before a macro parameter converts it to a string literal:

c
#define STRINGIFY(x)  #x
STRINGIFY(hello world)  /* becomes "hello world" */

Useful for printing expressions in debug macros.

Token Pasting (##)

The ## operator concatenates two tokens into one:

c
#define MAKE_VAR(n)  var_##n
MAKE_VAR(speed)  /* becomes var_speed */

This is used in code generation patterns where you build identifiers programmatically.

Variadic Macros (__VA_ARGS__)

C99 introduced variadic macros to accept a variable number of arguments:

c
#define PRINT(fmt, ...)  printf(fmt, ##__VA_ARGS__)

The ## before __VA_ARGS__ is a GCC extension that removes the preceding comma when no variadic arguments are passed, avoiding a syntax error.

inline Functions — The Safe Alternative

C99 introduced the inline keyword. An inline function has proper type checking, respects scope, evaluates arguments exactly once, and is a hint to the compiler to expand the call in place (avoiding function call overhead):

c
static inline int square(int x) { return x * x; }

For most purposes where you would use a function-like macro, an inline function is safer and equally efficient. Reserve macros for situations that genuinely require text substitution — like code generation with ## or wrapping __LINE__/__FILE__ capture.

When to Use Each

SituationUse
Numeric constant#define or const
Type-generic operationFunction-like macro
Performance-critical small functionstatic inline
Building identifiers at compile time## macro
Debug assert with source locationMacro (needs __LINE__)

Code Examples

Function-like Macro Pitfallc
#include <stdio.h>

/* DANGEROUS: missing parentheses */
#define BAD_SQUARE(x)   x * x
#define BAD_DOUBLE(x)   x + x

/* Safe: every parameter and the whole expression are parenthesized */
#define SAFE_SQUARE(x)  ((x) * (x))
#define SAFE_DOUBLE(x)  ((x) + (x))

int main(void) {
    int a = 3;

    /* Precedence trap */
    printf("BAD_SQUARE(1+2)  = %d\n", BAD_SQUARE(1+2));   /* 1+2*1+2 = 5, not 9 */
    printf("SAFE_SQUARE(1+2) = %d\n", SAFE_SQUARE(1+2));  /* ((1+2)*(1+2)) = 9  */

    /* Division trap: 10 / BAD_DOUBLE(5) -> 10 / 5 + 5 = 7, not 1 */
    printf("10 / BAD_DOUBLE(5)  = %d\n", 10 / BAD_DOUBLE(5));
    printf("10 / SAFE_DOUBLE(5) = %d\n", 10 / SAFE_DOUBLE(5));

    /* Side-effect trap */
    int i = 3;
    int result = SAFE_SQUARE(i++);  /* i++ evaluated TWICE — undefined behaviour */
    printf("i after SAFE_SQUARE(i++) = %d (i was incremented twice!)\n", i);
    (void)result;

    return 0;
}

BAD_SQUARE(1+2) expands to 1+2*1+2 because * binds tighter than +. The safe version wraps parameters so the expression is ((1+2)*(1+2)). The side-effect trap shows why you should never pass i++ to a macro — each occurrence of x in the expansion increments i separately.

Stringification and Token Pastingc
#include <stdio.h>

/* Stringification: # turns a token into a string literal */
#define STRINGIFY(x)   #x
#define TOSTRING(x)    STRINGIFY(x)   /* double expansion trick for macros as args */

/* Token pasting: ## concatenates two tokens */
#define MAKE_GETTER(type, field) \
    type get_##field(void) { return g_##field; }

/* Variadic debug macro using __VA_ARGS__ */
#define DEBUG_PRINT(fmt, ...) \
    printf("[%s:%d] " fmt "\n", __func__, __LINE__, ##__VA_ARGS__)

/* Global variables for the generated getters */
static int  g_speed  = 120;
static char g_name[] = "engine";

/* Token pasting generates these two functions: */
MAKE_GETTER(int,  speed)    /* int  get_speed(void) { return g_speed; } */
MAKE_GETTER(char*, name)    /* char* get_name(void) { return g_name; } */

#define VERSION_MAJOR 2
#define VERSION_MINOR 7
#define VERSION_STR   TOSTRING(VERSION_MAJOR) "." TOSTRING(VERSION_MINOR)

int main(void) {
    printf("Stringify: %s\n", STRINGIFY(hello world));
    printf("Version  : %s\n", VERSION_STR);
    printf("Speed    : %d\n", get_speed());
    printf("Name     : %s\n", get_name());
    DEBUG_PRINT("All systems nominal, speed=%d", get_speed());
    return 0;
}

STRINGIFY converts a token to a string. The double-expansion trick (TOSTRING calling STRINGIFY) is needed when the argument is itself a macro — without it, the macro name rather than its value would be stringified. MAKE_GETTER uses ## to build two different function names and variable names from a single macro invocation.

inline Functions as Safer Macro Alternativesc
#include <stdio.h>
#include <math.h>

/* Old-style macro — no type safety, side-effect prone */
#define MACRO_MAX(a, b)   ((a) > (b) ? (a) : (b))

/* C99 inline function — type safe, evaluates args once */
static inline int imax(int a, int b) {
    return a > b ? a : b;
}

static inline double dmax(double a, double b) {
    return a > b ? a : b;
}

/* Inline functions with __LINE__/__FILE__ still possible via wrapper macro */
#define ASSERT(cond) \
    do { \
        if (!(cond)) { \
            fprintf(stderr, "Assertion failed: %s\n  at %s:%d\n", \
                    #cond, __FILE__, __LINE__); \
        } \
    } while (0)

static inline double hypotenuse(double a, double b) {
    return sqrt(a * a + b * b);
}

int main(void) {
    int x = 5, y = 10;

    /* inline: y++ evaluated exactly once */
    printf("imax(%d, y++) = %d, y is now %d\n", x, imax(x, y++), y);

    int i = 5, j = 10;
    /* macro: j++ would be evaluated twice if j > i */
    printf("MACRO_MAX(%d, j++) = %d, j is now %d\n", i, MACRO_MAX(i, j++), j);

    printf("hypotenuse(3,4) = %.1f\n", hypotenuse(3.0, 4.0));
    printf("dmax(2.5, 3.7)  = %.1f\n", dmax(2.5, 3.7));

    ASSERT(x < y);       /* passes silently */
    ASSERT(x > 100);     /* prints assertion failure message */

    return 0;
}

The inline imax increments y exactly once. The MACRO_MAX increments j twice because the macro expands j++ in both the comparison and the result. This is the decisive reason to prefer inline functions. The do { } while(0) wrapper in ASSERT lets you use it safely in if/else chains without needing braces.

Quick Quiz

1. Why should every parameter of a function-like macro be wrapped in parentheses in the replacement text?

2. What does the # operator do inside a macro definition?

3. What is the main advantage of a static inline function over a function-like macro?

Was this lesson helpful?

PreviousNext