Skip to content

C Preprocessor

  • Before your program gets compiled, it actually runs through a phase called preprocessing. We’ve already seen this to an extent with #include! That’s the C Preprocessor! Where it sees that directive, it includes the named file right there, just as if you’d typed it in there. And then the compiler builds the whole thing.

Macros

Simple Macros

#include <stdio.h>
#include <stdlib.h>
#define HELLO "Hello, World"
#define PI 3.14159
int main(void){
printf("%s, %f\n", HELLO, PI);
}

Conditional Compilation

#include <stdio.h>
#include <stdlib.h>
#define HELLO "Hello, World"
#define PI 3.14159
int main(void){
#ifdef HELLO
printf("Hello mtfk\n");
#endif
}

Built-in macros

#include <stdio.h>
int main(void){
printf("This function: %s\n", __func__);
printf("This file: %s\n", __FILE__);
printf("This line: %d\n", __LINE__);
printf("Compiled on: %s %s\n", __DATE__, __TIME__);
printf("C Version: %ld\n",__STDC_VERSION__);
// This function: main
// This file: /Users/mc/Desktop/ostep/Chapter1/hello.c
// This line: 6
// Compiled on: Feb 29 2024 08:48:04
// C Version: 201710
}

Macros with arguments

  • One argument
#include <stdio.h>
#define SQR(x) ((x)*(x)) //
int main(void){
printf("%d\n", SQR(12));
//When compilers see this, it will change to
//printf("%d\n", ((12)*(12)));
}

Operator #

  • We can turn any argument into a string by preceding it with a # in the replacement text.
#define PRINT_INT(n) printf(#n " = %d\n", n)
PRINT_INT(i/j);
// equivalent to printf("i/j" " = %d\n", i/j);
// or printf("i/j = %d\n", i/j);
#define STR(x) #x
printf("%s\n", STR(3.14159));

Operator ##

  • We can concatenate 2 arguments together with ##:
#define CAT(a,b) a ## b
printf("%f\n", CAT(3.14, 1592)); // 3.141592
#define JOIN(x,y,z) x##y##z
int JOIN(a,b,c), JOIN(a,b,), JOIN(a,,c), JOIN(,,c);
// int abc, ab, ac, c;

Macros with a variable number of arguments

#define X(a, b, ...) (10*(a) + 20*(b)), __VA_ARGS__
int main(void){
printf("%d %f %s %d\n", X(5, 4, 3.14, "Hi!", 12));
// equivalent to
// printf("%d %f %s %d\n", (10*(5) + 20*(4)), 3.14, "Hi!", 12);
}

The func identifier

  • The __func__ identifier is a string variable that stores the name of the currently executing function => makes it possible to write debugging macros such as the following:
#define FUNCTION_CALLED() printf("%s called\n", __func__);
#define FUNCTION_RETURNS() printf("%s returns\n", __func__);
void f(void)
{
FUNCTION_CALLED(); /* displays "f called" */
//function main
FUNCTION_RETURNS(); /* displays "f returns" */
}

Conditional compilation

#if and #endif

#define DEBUG 1
#if DEBUG
printf("Value of i: %d\n", i);
printf("Value of j: %d\n", j);
#endif

#defined operator

#if defined(DEBUG)
...
#endif
  • Equivalent to
#ifdef DEBUG
...
#endif

#error directive

  • This directive causes the compiler to error out as soon as it sees it. Commonly, this is used inside a conditional to prevent compilation unless some prerequisites are met
#if defined(WIN32)
#elif defined(MAC_OS)
#elif defined(LINUX)
#else
#error No operating system specified
#endif

Uses of conditional compilation

  • Writing programs that are portable to several machines or operating systems.
#if defined(WIN32)
...
#elif defined(MAC_OS)
...
#elif defined(LINUX)
...
#endif
  • Writing programs that can be compiled with different compilers
#if __STDC__
Function prototypes
#else
Old-style function declarations
#endif
  • Provide a default definition for a macro
#ifndef BUFFER_SIZE
#define BUFFER_SIZE 256
#endif
  • **Protecting header files against multiple inclusion.