Skip to Content
Course content

69: Function-Like Macros

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A lot of people coming into C think that function-like macros are just a shorthand for writing small, inline functions. They see #define MAX(a,b) ((a) > (b) ? (a) : (b)) and assume the compiler is treating it as a tiny function call that happens to be fast. It looks like a function, it takes arguments, and it returns a value. But here is the thing: the compiler never even sees your macro.

The "Macros are just fast functions" fallacy

The preprocessor is essentially a sophisticated "find and replace" tool. It doesn't understand C types, scope, or logic; it just shuffles text around before the actual compilation begins. If you treat a macro like a function, you're going to run into some truly bizarre bugs that are a nightmare to debug because the code you're looking at in your editor isn't the code the compiler is actually processing.

Take this seemingly innocent macro for squaring a number:

#define SQUARE(x) x * x

int main() {
    int result = SQUARE(1 + 1); 
    // You expect 4, but you get 3.
}

Why 3? Because the preprocessor does a literal substitution. The line becomes int result = 1 + 1 * 1 + 1;. Following the standard order of operations, multiplication happens first: 1 + (1 * 1) + 1, which equals 3. I've spent more time than I'd like to admit chasing bugs exactly like this because I forgot that macros are just text manipulation.

Parenthesize everything to survive the preprocessor

To make a macro behave predictably, you have to be paranoid. You must wrap every single argument in parentheses, and then wrap the entire expression in another set of parentheses. This forces the order of operations to remain what you intended, regardless of what the caller passes in.

Here is the professional way to write that same macro:

#define SQUARE(x) ((x) * (x))

Now, SQUARE(1 + 1) expands to ((1 + 1) * (1 + 1)), which correctly evaluates to 4. It looks ugly and cluttered, but in the world of macros, "ugly" is usually synonymous with "safe."

The double-evaluation trap

Even with parentheses, macros have a dangerous quirk that actual functions don't: they evaluate their arguments every time they appear in the macro body. If you pass an expression with a side effect—like an increment operator—you're in for a surprise.

Consider this scenario:

#define SQUARE(x) ((x) * (x))

int a = 5;
int result = SQUARE(a++); 
// You might expect result to be 25 and a to be 6.
// In reality, result is 30 (5 * 6) and a is 7.

Because a++ is substituted twice, the code becomes ((a++) * (a++)). The variable is incremented twice. This is one of the primary reasons why, in modern C, I generally prefer static inline functions over macros whenever possible. Inline functions provide the same performance benefit but obey the rules of the C language, including type checking and single-evaluation of arguments.

Use macros when you absolutely have to—like when you need to use stringification (#) or token pasting (##), or when you're doing something that must happen before the compiler starts its work. Otherwise, stick to functions.




📋 Practical Task

Fixing the Volatile Buffer Boundary Macro

You've inherited a legacy driver for a hardware buffer. The previous engineer wrote a macro to calculate the offset of a memory address, but it's causing sporadic crashes because it doesn't handle operator precedence correctly and it suffers from the double-evaluation problem.

Your task:

  1. Rewrite the GET_OFFSET macro so that it is safe from operator precedence issues (e.g., it should work if the user passes base + 1).
  2. Modify the main function to avoid the double-evaluation bug where ptr++ was being passed into the macro.
#include <stdio.h>

// BUGGY MACRO: Needs parentheses!
#define GET_OFFSET(base, index) base + (index * 4)

int main() {
    int buffer[10] = {0};
    int *ptr = buffer;

    // This currently expands to: ptr++ + (2 * 4)
    // It increments ptr twice or behaves unpredictably.
    int offset = GET_OFFSET(ptr++, 2); 
    
    printf("Offset: %d\n", offset);
    return 0;
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.