{"article":{"slug":"fun-extending-the-c-preprocessor","title":"Fun Extending the C Preprocessor","subtitle":null,"summary":"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.","content_type":"blog_post","language":"en","canonical_url":"https://www.davidpriver.com/Fun-Extending-the-C-Preprocessor.html","author":{"name":"David Priver","url":"https://www.davidpriver.com/","person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"David Priver","url":"https://www.davidpriver.com/","listing_slug":null,"listing":null},"topics":[{"name":"C","slug":"c","url":"https://listedarticles.com/topics/c"},{"name":"Compilers","slug":"compilers","url":"https://listedarticles.com/topics/compilers"},{"name":"Programming Languages","slug":"programming-languages","url":"https://listedarticles.com/topics/programming-languages"}],"about_listings":[],"cover_image_url":null,"license":"all-rights-reserved","word_count":1147,"reading_minutes":5,"published_at":"2026-10-13T00:00:00.000Z","added_at":"2026-10-18T08:14:53.153Z","updated_at":"2026-10-18T08:14:53.153Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":true},"profile_url":"https://listedarticles.com/articles/fun-extending-the-c-preprocessor","markdown_url":"https://listedarticles.com/articles/fun-extending-the-c-preprocessor.md","example":false,"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)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://www.davidpriver.com/Fun-Extending-the-C-Preprocessor.html"},"body_markdown":"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\n[C2Y Compiler / Interpreter / REPL](https://github.com/drpriver/DrC) for fun.\nOne of the first things one has to do when writing a C Compiler is to first\nwrite a C Preprocessor (also known as the CPP). Counter to what most people\nthink of the C Preprocessor, it is not a text-to-text translator. Instead\nit is a source to C token translator, with most implementations offering a\ntext output to see what it does. Historically, it might have been implemented\nas a text-to-text translator, but the C standard specifies it as a series of\nphases that ends up with C tokens that are consumed by the C parser until you\neventually end up with a C translation unit.\n\nOthers have probably written about how to write a C Preprocessor before, so I\nwon't focus on that. Instead I'll focus on some fun ways I've extended the C\nPreprocessor, either to make it easier to use or just because I thought it was\ngoofy. I won't list [all of them](https://github.com/drpriver/DrC/blob/master/EXTENSIONS.md#preprocessor), but just the particularly interesting ones.\n\n## \\_\\_print()/\\_\\_where()\n\nThe most straightforward extensions: builtin debugging macros.\n\n\\_\\_print(...) acts as a preprocessor print function, expanding its arg(s)\nand writing it to stderr, useful for debugging what a macro expands to.\nAs you only get exactly what you \\_\\_print(), it can be much easier to\nsee what the preprocessor expands to without dumping the entire translation\nunit like with cpp.\n\n```\n$ printf \"#define X 1\\n__print(X   X)\" | drcpp\n(stdin):2:1: 1   1\n```\n\nEver have trouble figuring out where the heck a macro was defined that is\nexpanding in some weird way? Maybe some baroque glibc macro is ruining your life?\n\\_\\_where() is your friend. It writes to stderr where the macro was defined.\n\n```\n$ printf \"#define X 1\\n\\n\\n\\n__where(X)\" | drcpp\n(stdin):1:9: info: X defined\n```\n\n## \\_\\_map()\n\nSomething that occasionally comes up is the need to apply some transformation\nto each argument in a list. Perhaps you want to print a sequence of values\nyou have in a preprocessor sequence. The standard functional way to\nimplement this would be to write a recursive function (in the preprocessor a\nfunction-like macro), but the C standard blocks direct recursion as the\ntokens get \"painted blue\" and have their expansion suppressed. However, you\ngenerally don't need arbitrarily long recursion, so you can fake it by\npumping the recursion yourself for a fixed number of iterations. If you have an\naversion to disgusting preprocessor abuse, then don't look at this example:\n\nI warned you\n\n```\n// map.c\n#define CAT(a, b) CAT_(a, b)\n#define CAT_(a, b) a##b\n#define NARG(...) \\\n  NARG_(__VA_ARGS__ __VA_OPT__(,) 8,7,6,5,4,3,2,1,0)\n#define NARG_(_1,_2,_3,_4,_5,_6,_7,_8,N,...) N\n#define MAP_0(m)\n#define MAP_1(m, x)      m(x)\n#define MAP_2(m, x, ...) m(x) MAP_1(m, __VA_ARGS__)\n#define MAP_3(m, x, ...) m(x) MAP_2(m, __VA_ARGS__)\n#define MAP_4(m, x, ...) m(x) MAP_3(m, __VA_ARGS__)\n#define MAP_5(m, x, ...) m(x) MAP_4(m, __VA_ARGS__)\n#define MAP_6(m, x, ...) m(x) MAP_5(m, __VA_ARGS__)\n#define MAP_7(m, x, ...) m(x) MAP_6(m, __VA_ARGS__)\n#define MAP_8(m, x, ...) m(x) MAP_7(m, __VA_ARGS__)\n#define __map(m, ...) \\\n  CAT(MAP_, NARG(__VA_ARGS__))(m __VA_OPT__(,) __VA_ARGS__)\nint x = 1;\nint y = 2;\nint z = 3;\n\n#define PRINTINT(x) printf(#x \" = %d\\n\", x);\nint printf(const char*, ...);\nint main(){\n    __map(PRINTINT, 1, 2, 3, 4, x, y, z)\n}\n```\n\nWhich expands to (whitespace inserted for readability):\n\n```\n// map.i\nint x = 1;\nint y = 2;\nint z = 3;\nint printf(const char*, ...);\nint main(){\n    printf(\"1\" \" = %d\\n\", 1); // 1 = 1\n    printf(\"2\" \" = %d\\n\", 2); // 2 = 2\n    printf(\"3\" \" = %d\\n\", 3); // 3 = 3\n    printf(\"4\" \" = %d\\n\", 4); // 4 = 4\n    printf(\"x\" \" = %d\\n\", x); // x = 1\n    printf(\"y\" \" = %d\\n\", y); // y = 2\n    printf(\"z\" \" = %d\\n\", z); // z = 3\n}\n```\n\nThis is useful, but obviously pretty disgusting and is generally quite slow as you\nare abusing preprocessor expansion, which has to scan and expand each argument of\neach function-like macro over and over again. So I added \\_\\_map(macro, ...) to my C\npreprocessor:\n\nWith a builtin \\_\\_map(), the above example simplifies to just:\n\n```\nint x = 1;\nint y = 2;\nint z = 3;\n#define PRINTINT(x) printf(#x \" = %d\\n\", x);\nint printf(const char*, ...);\nint main(){\n    __map(PRINTINT, 1, 2, 3, 4, x, y, z)\n}\n```\n\nNow you don't have to mess around with faking recursion: it can apply the\nmacro to each argument once in a linear scan. And you have no arbitrary\nlimit on how many items you can map!\n\n## \\_\\_VA\\_COUNT\\_\\_\n\nC23 made working with variadic macros more convenient and powerful with\n\\_\\_VA\\_OPT\\_\\_(), but some use-cases remain difficult. As demonstrated in the\nprevious section, it can be useful to \"overload\" a macro by its arity, but\nthis requires gross manual preprocessor pumping.\n\nAnother use case is passing a count + pointer to a function if your compiler\nstill hasn't [fixed C's biggest mistake](https://www.davidpriver.com/fixing-c-biggest-mistake.html), or a param count to a variadic\nfunction.\n\n```\n// sum.c\n#define CAT(a, b) CAT_(a, b)\n#define CAT_(a, b) a##b\n#define NARG(...) \\\n  NARG_(__VA_ARGS__ __VA_OPT__(,) 8,7,6,5,4,3,2,1,0)\n#define NARG_(_1,_2,_3,_4,_5,_6,_7,_8,N,...) N\nint sum(size_t count, int* data){\n    int result = 0;\n    for(size_t i = 0; i < count; i++){\n        result += data[i];\n    }\n    return result;\n}\n#define sum(...) sum(NARG(__VA_ARGS__), (int[]){__VA_ARGS__})\nint printf(const char*, ...);\nint main(){\n    printf(\"%d\\n\", sum(1,2,3)); // 6\n    printf(\"%d\\n\", sum(4,5)); // 9\n}\n```\n\nInstead of going through all of this nonsense to just get the number of\nvariadic arguments (which the cpp already has!), I added a builtin macro\n\\_\\_VA\\_COUNT\\_\\_, so you can just get the count of variadic arguments\ndirectly.\n\n```\n// sum-drc.c\nint sum(size_t count, int* data){\n    int result = 0;\n    for(size_t i = 0; i < count; i++){\n        result += data[i];\n    }\n    return result;\n}\n#define sum(...) sum(__VA_COUNT__, (int[]){__VA_ARGS__})\nint printf(const char*, ...);\nint main(){\n    printf(\"%d\\n\", sum(1,2,3)); // 6\n    printf(\"%d\\n\", sum(4,5)); // 9\n}\n```\n\nNicer and faster!\n\n## Conclusion\n\nThe C Preprocessor is a powerful tool, but requires some unreadable dark\nmagic to really take advantage of it. This isn't inherent to what you want to\ndo with it though - it is merely a consequence of its underdeveloped\nmachinery. With some extensions, we can make the CPP more powerful while\nalso improving readability and making it more enjoyable to use.\n\nI have more to talk about, including some true dark magic like mutable\npreprocessor state, but that's it for this post!\n","body_html":"<p>As I&#39;ve mentioned in <a href=\"https://www.davidpriver.com/fixing-c-biggest-mistake.html\" rel=\"nofollow ugc noopener\">previous</a> <a href=\"https://www.davidpriver.com/From-Tree-Walker-to-Bytecode.html\" rel=\"nofollow ugc noopener\">posts</a>, I&#39;ve been writing my own\n<a href=\"https://github.com/drpriver/DrC\" rel=\"nofollow ugc noopener\">C2Y Compiler / Interpreter / REPL</a> for fun.\nOne of the first things one has to do when writing a C Compiler is to first\nwrite a C Preprocessor (also known as the CPP). Counter to what most people\nthink of the C Preprocessor, it is not a text-to-text translator. Instead\nit is a source to C token translator, with most implementations offering a\ntext output to see what it does. Historically, it might have been implemented\nas a text-to-text translator, but the C standard specifies it as a series of\nphases that ends up with C tokens that are consumed by the C parser until you\neventually end up with a C translation unit.</p>\n<p>Others have probably written about how to write a C Preprocessor before, so I\nwon&#39;t focus on that. Instead I&#39;ll focus on some fun ways I&#39;ve extended the C\nPreprocessor, either to make it easier to use or just because I thought it was\ngoofy. I won&#39;t list <a href=\"https://github.com/drpriver/DrC/blob/master/EXTENSIONS.md#preprocessor\" rel=\"nofollow ugc noopener\">all of them</a>, but just the particularly interesting ones.</p>\n<h2 id=\"print-where\">__print()/__where()</h2>\n<p>The most straightforward extensions: builtin debugging macros.</p>\n<p>__print(...) acts as a preprocessor print function, expanding its arg(s)\nand writing it to stderr, useful for debugging what a macro expands to.\nAs you only get exactly what you __print(), it can be much easier to\nsee what the preprocessor expands to without dumping the entire translation\nunit like with cpp.</p>\n<pre><code>$ printf &quot;#define X 1\\n__print(X   X)&quot; | drcpp\n(stdin):2:1: 1   1</code></pre>\n<p>Ever have trouble figuring out where the heck a macro was defined that is\nexpanding in some weird way? Maybe some baroque glibc macro is ruining your life?\n__where() is your friend. It writes to stderr where the macro was defined.</p>\n<pre><code>$ printf &quot;#define X 1\\n\\n\\n\\n__where(X)&quot; | drcpp\n(stdin):1:9: info: X defined</code></pre>\n<h2 id=\"map\">__map()</h2>\n<p>Something that occasionally comes up is the need to apply some transformation\nto each argument in a list. Perhaps you want to print a sequence of values\nyou have in a preprocessor sequence. The standard functional way to\nimplement this would be to write a recursive function (in the preprocessor a\nfunction-like macro), but the C standard blocks direct recursion as the\ntokens get &quot;painted blue&quot; and have their expansion suppressed. However, you\ngenerally don&#39;t need arbitrarily long recursion, so you can fake it by\npumping the recursion yourself for a fixed number of iterations. If you have an\naversion to disgusting preprocessor abuse, then don&#39;t look at this example:</p>\n<p>I warned you</p>\n<pre><code>// map.c\n#define CAT(a, b) CAT_(a, b)\n#define CAT_(a, b) a##b\n#define NARG(...) \\\n  NARG_(__VA_ARGS__ __VA_OPT__(,) 8,7,6,5,4,3,2,1,0)\n#define NARG_(_1,_2,_3,_4,_5,_6,_7,_8,N,...) N\n#define MAP_0(m)\n#define MAP_1(m, x)      m(x)\n#define MAP_2(m, x, ...) m(x) MAP_1(m, __VA_ARGS__)\n#define MAP_3(m, x, ...) m(x) MAP_2(m, __VA_ARGS__)\n#define MAP_4(m, x, ...) m(x) MAP_3(m, __VA_ARGS__)\n#define MAP_5(m, x, ...) m(x) MAP_4(m, __VA_ARGS__)\n#define MAP_6(m, x, ...) m(x) MAP_5(m, __VA_ARGS__)\n#define MAP_7(m, x, ...) m(x) MAP_6(m, __VA_ARGS__)\n#define MAP_8(m, x, ...) m(x) MAP_7(m, __VA_ARGS__)\n#define __map(m, ...) \\\n  CAT(MAP_, NARG(__VA_ARGS__))(m __VA_OPT__(,) __VA_ARGS__)\nint x = 1;\nint y = 2;\nint z = 3;\n\n#define PRINTINT(x) printf(#x &quot; = %d\\n&quot;, x);\nint printf(const char*, ...);\nint main(){\n    __map(PRINTINT, 1, 2, 3, 4, x, y, z)\n}</code></pre>\n<p>Which expands to (whitespace inserted for readability):</p>\n<pre><code>// map.i\nint x = 1;\nint y = 2;\nint z = 3;\nint printf(const char*, ...);\nint main(){\n    printf(&quot;1&quot; &quot; = %d\\n&quot;, 1); // 1 = 1\n    printf(&quot;2&quot; &quot; = %d\\n&quot;, 2); // 2 = 2\n    printf(&quot;3&quot; &quot; = %d\\n&quot;, 3); // 3 = 3\n    printf(&quot;4&quot; &quot; = %d\\n&quot;, 4); // 4 = 4\n    printf(&quot;x&quot; &quot; = %d\\n&quot;, x); // x = 1\n    printf(&quot;y&quot; &quot; = %d\\n&quot;, y); // y = 2\n    printf(&quot;z&quot; &quot; = %d\\n&quot;, z); // z = 3\n}</code></pre>\n<p>This is useful, but obviously pretty disgusting and is generally quite slow as you\nare abusing preprocessor expansion, which has to scan and expand each argument of\neach function-like macro over and over again. So I added __map(macro, ...) to my C\npreprocessor:</p>\n<p>With a builtin __map(), the above example simplifies to just:</p>\n<pre><code>int x = 1;\nint y = 2;\nint z = 3;\n#define PRINTINT(x) printf(#x &quot; = %d\\n&quot;, x);\nint printf(const char*, ...);\nint main(){\n    __map(PRINTINT, 1, 2, 3, 4, x, y, z)\n}</code></pre>\n<p>Now you don&#39;t have to mess around with faking recursion: it can apply the\nmacro to each argument once in a linear scan. And you have no arbitrary\nlimit on how many items you can map!</p>\n<h2 id=\"va-count\"><strong>VA_COUNT</strong></h2>\n<p>C23 made working with variadic macros more convenient and powerful with\n<strong>VA_OPT</strong>(), but some use-cases remain difficult. As demonstrated in the\nprevious section, it can be useful to &quot;overload&quot; a macro by its arity, but\nthis requires gross manual preprocessor pumping.</p>\n<p>Another use case is passing a count + pointer to a function if your compiler\nstill hasn&#39;t <a href=\"https://www.davidpriver.com/fixing-c-biggest-mistake.html\" rel=\"nofollow ugc noopener\">fixed C&#39;s biggest mistake</a>, or a param count to a variadic\nfunction.</p>\n<pre><code>// sum.c\n#define CAT(a, b) CAT_(a, b)\n#define CAT_(a, b) a##b\n#define NARG(...) \\\n  NARG_(__VA_ARGS__ __VA_OPT__(,) 8,7,6,5,4,3,2,1,0)\n#define NARG_(_1,_2,_3,_4,_5,_6,_7,_8,N,...) N\nint sum(size_t count, int* data){\n    int result = 0;\n    for(size_t i = 0; i &lt; count; i++){\n        result += data[i];\n    }\n    return result;\n}\n#define sum(...) sum(NARG(__VA_ARGS__), (int[]){__VA_ARGS__})\nint printf(const char*, ...);\nint main(){\n    printf(&quot;%d\\n&quot;, sum(1,2,3)); // 6\n    printf(&quot;%d\\n&quot;, sum(4,5)); // 9\n}</code></pre>\n<p>Instead of going through all of this nonsense to just get the number of\nvariadic arguments (which the cpp already has!), I added a builtin macro\n<strong>VA_COUNT</strong>, so you can just get the count of variadic arguments\ndirectly.</p>\n<pre><code>// sum-drc.c\nint sum(size_t count, int* data){\n    int result = 0;\n    for(size_t i = 0; i &lt; count; i++){\n        result += data[i];\n    }\n    return result;\n}\n#define sum(...) sum(__VA_COUNT__, (int[]){__VA_ARGS__})\nint printf(const char*, ...);\nint main(){\n    printf(&quot;%d\\n&quot;, sum(1,2,3)); // 6\n    printf(&quot;%d\\n&quot;, sum(4,5)); // 9\n}</code></pre>\n<p>Nicer and faster!</p>\n<h2 id=\"conclusion\">Conclusion</h2>\n<p>The C Preprocessor is a powerful tool, but requires some unreadable dark\nmagic to really take advantage of it. This isn&#39;t inherent to what you want to\ndo with it though - it is merely a consequence of its underdeveloped\nmachinery. With some extensions, we can make the CPP more powerful while\nalso improving readability and making it more enjoyable to use.</p>\n<p>I have more to talk about, including some true dark magic like mutable\npreprocessor state, but that&#39;s it for this post!</p>","headings":[{"level":2,"text":"__print()/__where()","id":"print-where"},{"level":2,"text":"__map()","id":"map"},{"level":2,"text":"__VA_COUNT__","id":"va-count"},{"level":2,"text":"Conclusion","id":"conclusion"}]}}