Preprocessing

The compilation of any C program begins with a preprocessing that handles the lines of the program beginning with #.  This preprocessing is done by a module of the compiler known as preprocessor or precompiler.

The ideia of a program that has two logicals levels, with one syntax for some lines of code and another for the other lines, may seem inelegant. From a practical point of view, however, this idea is very useful and powerful.

The present site uses only two preprocessing directives#define and #include.

Table of contents:

The define directive

The define directive establishes a shorthand — known as a macro — for a piece of code.  For example, the line

#define MAX 1000

establishes MAX as a shorthand for 1000.  The sequence of characters that begins after MAX and goes ⚠ to the end of the line is the meaning of the shorthand, that is, the value of the macro.  (If you write a ; at the end of the line, this character will be part of the macro.)

Example 1.  The program

#define MAX 1000

int main (void) {
   int v[MAX];
   for (int i = 0; i < MAX; ++i) {
      . . .
   }
   return EXIT_SUCCESS;
} 

is transformed by the preprocessor into

int main (void) {
   int v[1000];
   for (int i = 0; i < 1000; ++i) {
      . . .
   }
   return EXIT_SUCCESS;
} 

Example 2.  Much like a function, a macro can have parameters. Hence, the piece of program

#define swap (A, B) {int t = A; A = B; B = t;} 

for (int i = 0; i < 100; ++i) {
   int k = i;
   for (int j = i+1; j <= 100; ++j) 
      if (a[j] < a[k]) k = j;
   swap (a[i], a[k]);
} 

is transformed by the preprocessor into

for (int i = 0; i < 100; ++i) {
   int k = i;
   for (int j = i+1; j <= 100; ++j) 
      if (a[j] < a[k]) k = j;
   {int t = a[i]; a[i] = a[k]; a[k] = t;}
} 

The include directive

The include directive adds to your program the contents of the specified file.

Example 1.  Suppose that a file aaa.txt in the current directory has the following contents:

int GLOB = 8; // global variable
int func (int e); // prototype of function

Then the program

#include "aaa.txt"

int main (void) { 
   while (GLOB <= 64) {
      int y = func (5);
      printf ("%d\n", y);
      GLOB *= 2;
   }
   return EXIT_SUCCESS;
}

int func (int i) { // computes GLOB^i
   . . .
}

will be transformed by the preprocessor into

int GLOB = 8;
int func (int e);

int main (void) { 
   while (GLOB <= 64) {
      int y = func (5);
      printf ("%d\n", y);
      GLOB *= 2;
   }
   return EXIT_SUCCESS;
}

int func (int i) { // computes GLOB^i
   . . .
}

Example 2.  If the name of the file to be included is wrapped in < and > (rather than double quotes), the preprocessor will look for it in an appropriate system directory (usually /usr/include/) instead of the current directory:

#include <aaa.txt>

int main (void) { 
   . . .
}

This is used, in general, to include standard library interfaces.

Example 3.  The file to be included may contain other #includes and #defines. The expansion of the directives is recursive.  Suppose, for example, that the file aaa.txt in the current directory has the following contents:

#define PI 3.14159
#include <math.h>

Then the program

#include "aaa.txt"

int main (void) { 
    double y = sin (PI/4);
    printf ("%f\n", y);
    return EXIT_SUCCESS;
}

will be transformed by the preprocessor into

. . .
. . .

int main (void) { 
    double y = sin (3.14159/4);
    printf ("%f\n", y);
    return EXIT_SUCCESS;
}

where the lines  . . .  represent the contents of the math.h file.

The preprocessed version of a program

The result of the preprocessing of a program is a clean version of the program, without preprocessing directives.  The programmer does not need to handle this version, since it will be automatically submitted to the noble module of the compiler.  But, he can examine the clean version if he so desires: the command line

~/my-work-dir$ gcc -E xxx.c > yyy.c

(notice the -E option)  submits the source file xxx.c to the compiler and saves the preprocessed version of the program to the file yyy.c.