---
title: "Fun Extending the C Preprocessor"
slug: fun-extending-the-c-preprocessor
url: https://listedarticles.com/articles/fun-extending-the-c-preprocessor
canonical_url: https://www.davidpriver.com/Fun-Extending-the-C-Preprocessor.html
content_type: blog_post
language: en
published_at: 2026-10-13T00:00:00.000Z
updated_at: 2026-10-18T08:14:53.153Z
author: "David Priver"
author_url: https://www.davidpriver.com/
authored_by: human
publisher: "David Priver"
publisher_url: https://www.davidpriver.com/
topics: ["C", "Compilers", "Programming Languages"]
license: all-rights-reserved
word_count: 1147
reading_minutes: 5
citation: "David Priver, David Priver. \"Fun Extending the C Preprocessor.\" 13 Oct 2026. https://www.davidpriver.com/Fun-Extending-the-C-Preprocessor.html (all-rights-reserved)"
# The full text follows. The web page shows an extract and sends readers
# to the source above; quote the citation and link the canonical URL.
---

# Fun Extending the C Preprocessor

> David Priver, who is writing his own C2Y compiler/interpreter/REPL, shows extensions he added to its C preprocessor, including __print() and __where() debugging builtins, a native __map() that replaces recursive macro hacks, and __VA_COUNT__ for counting variadic arguments.

As I've mentioned in [previous](https://www.davidpriver.com/fixing-c-biggest-mistake.html) [posts](https://www.davidpriver.com/From-Tree-Walker-to-Bytecode.html), I've been writing my own
[C2Y Compiler / Interpreter / REPL](https://github.com/drpriver/DrC) for fun.
One of the first things one has to do when writing a C Compiler is to first
write a C Preprocessor (also known as the CPP). Counter to what most people
think of the C Preprocessor, it is not a text-to-text translator. Instead
it is a source to C token translator, with most implementations offering a
text output to see what it does. Historically, it might have been implemented
as a text-to-text translator, but the C standard specifies it as a series of
phases that ends up with C tokens that are consumed by the C parser until you
eventually end up with a C translation unit.

Others have probably written about how to write a C Preprocessor before, so I
won't focus on that. Instead I'll focus on some fun ways I've extended the C
Preprocessor, either to make it easier to use or just because I thought it was
goofy. I won't list [all of them](https://github.com/drpriver/DrC/blob/master/EXTENSIONS.md#preprocessor), but just the particularly interesting ones.

## \_\_print()/\_\_where()

The most straightforward extensions: builtin debugging macros.

\_\_print(...) acts as a preprocessor print function, expanding its arg(s)
and writing it to stderr, useful for debugging what a macro expands to.
As you only get exactly what you \_\_print(), it can be much easier to
see what the preprocessor expands to without dumping the entire translation
unit like with cpp.

```
$ printf "#define X 1\n__print(X   X)" | drcpp
(stdin):2:1: 1   1
```

Ever have trouble figuring out where the heck a macro was defined that is
expanding in some weird way? Maybe some baroque glibc macro is ruining your life?
\_\_where() is your friend. It writes to stderr where the macro was defined.

```
$ printf "#define X 1\n\n\n\n__where(X)" | drcpp
(stdin):1:9: info: X defined
```

## \_\_map()

Something that occasionally comes up is the need to apply some transformation
to each argument in a list. Perhaps you want to print a sequence of values
you have in a preprocessor sequence. The standard functional way to
implement this would be to write a recursive function (in the preprocessor a
function-like macro), but the C standard blocks direct recursion as the
tokens get "painted blue" and have their expansion suppressed. However, you
generally don't need arbitrarily long recursion, so you can fake it by
pumping the recursion yourself for a fixed number of iterations. If you have an
aversion to disgusting preprocessor abuse, then don't look at this example:

I warned you

```
// map.c
#define CAT(a, b) CAT_(a, b)
#define CAT_(a, b) a##b
#define NARG(...) \
  NARG_(__VA_ARGS__ __VA_OPT__(,) 8,7,6,5,4,3,2,1,0)
#define NARG_(_1,_2,_3,_4,_5,_6,_7,_8,N,...) N
#define MAP_0(m)
#define MAP_1(m, x)      m(x)
#define MAP_2(m, x, ...) m(x) MAP_1(m, __VA_ARGS__)
#define MAP_3(m, x, ...) m(x) MAP_2(m, __VA_ARGS__)
#define MAP_4(m, x, ...) m(x) MAP_3(m, __VA_ARGS__)
#define MAP_5(m, x, ...) m(x) MAP_4(m, __VA_ARGS__)
#define MAP_6(m, x, ...) m(x) MAP_5(m, __VA_ARGS__)
#define MAP_7(m, x, ...) m(x) MAP_6(m, __VA_ARGS__)
#define MAP_8(m, x, ...) m(x) MAP_7(m, __VA_ARGS__)
#define __map(m, ...) \
  CAT(MAP_, NARG(__VA_ARGS__))(m __VA_OPT__(,) __VA_ARGS__)
int x = 1;
int y = 2;
int z = 3;

#define PRINTINT(x) printf(#x " = %d\n", x);
int printf(const char*, ...);
int main(){
    __map(PRINTINT, 1, 2, 3, 4, x, y, z)
}
```

Which expands to (whitespace inserted for readability):

```
// map.i
int x = 1;
int y = 2;
int z = 3;
int printf(const char*, ...);
int main(){
    printf("1" " = %d\n", 1); // 1 = 1
    printf("2" " = %d\n", 2); // 2 = 2
    printf("3" " = %d\n", 3); // 3 = 3
    printf("4" " = %d\n", 4); // 4 = 4
    printf("x" " = %d\n", x); // x = 1
    printf("y" " = %d\n", y); // y = 2
    printf("z" " = %d\n", z); // z = 3
}
```

This is useful, but obviously pretty disgusting and is generally quite slow as you
are abusing preprocessor expansion, which has to scan and expand each argument of
each function-like macro over and over again. So I added \_\_map(macro, ...) to my C
preprocessor:

With a builtin \_\_map(), the above example simplifies to just:

```
int x = 1;
int y = 2;
int z = 3;
#define PRINTINT(x) printf(#x " = %d\n", x);
int printf(const char*, ...);
int main(){
    __map(PRINTINT, 1, 2, 3, 4, x, y, z)
}
```

Now you don't have to mess around with faking recursion: it can apply the
macro to each argument once in a linear scan. And you have no arbitrary
limit on how many items you can map!

## \_\_VA\_COUNT\_\_

C23 made working with variadic macros more convenient and powerful with
\_\_VA\_OPT\_\_(), but some use-cases remain difficult. As demonstrated in the
previous section, it can be useful to "overload" a macro by its arity, but
this requires gross manual preprocessor pumping.

Another use case is passing a count + pointer to a function if your compiler
still hasn't [fixed C's biggest mistake](https://www.davidpriver.com/fixing-c-biggest-mistake.html), or a param count to a variadic
function.

```
// sum.c
#define CAT(a, b) CAT_(a, b)
#define CAT_(a, b) a##b
#define NARG(...) \
  NARG_(__VA_ARGS__ __VA_OPT__(,) 8,7,6,5,4,3,2,1,0)
#define NARG_(_1,_2,_3,_4,_5,_6,_7,_8,N,...) N
int sum(size_t count, int* data){
    int result = 0;
    for(size_t i = 0; i < count; i++){
        result += data[i];
    }
    return result;
}
#define sum(...) sum(NARG(__VA_ARGS__), (int[]){__VA_ARGS__})
int printf(const char*, ...);
int main(){
    printf("%d\n", sum(1,2,3)); // 6
    printf("%d\n", sum(4,5)); // 9
}
```

Instead of going through all of this nonsense to just get the number of
variadic arguments (which the cpp already has!), I added a builtin macro
\_\_VA\_COUNT\_\_, so you can just get the count of variadic arguments
directly.

```
// sum-drc.c
int sum(size_t count, int* data){
    int result = 0;
    for(size_t i = 0; i < count; i++){
        result += data[i];
    }
    return result;
}
#define sum(...) sum(__VA_COUNT__, (int[]){__VA_ARGS__})
int printf(const char*, ...);
int main(){
    printf("%d\n", sum(1,2,3)); // 6
    printf("%d\n", sum(4,5)); // 9
}
```

Nicer and faster!

## Conclusion

The C Preprocessor is a powerful tool, but requires some unreadable dark
magic to really take advantage of it. This isn't inherent to what you want to
do with it though - it is merely a consequence of its underdeveloped
machinery. With some extensions, we can make the CPP more powerful while
also improving readability and making it more enjoyable to use.

I have more to talk about, including some true dark magic like mutable
preprocessor state, but that's it for this post!
