Preprocessors

C Language

Introduction

In this lesson, we are going to discuss a very easy concept in C (as a break after pointers): preprocessors. Preprocessors are taken care of as the first step of compilation of your program. The C preprocessors include the file inclusion preprocessor (#include), the macro substitution (#define), and some conditional preprocessors (#if, etc).


Preprocessors

You have already been using C preprocessors by using the #include statement to include headerfiles to your program. We will discuss the easiest: the #include preprocessor first.

#include

The #include preprocessor "include" a file to your program. We have already did this through including headerfiles: <stdio.h>, <string.h>, <stdlib.h>, etc. Through including those headerfiles, your program "copies" the file into your program and therefore, make functions declared in that headerfile available for you to use. You can also use this preprocessors to include your program files. This is useful when your programs get longer, fitting them all inside one file would be unrealistic. You can write your functions in other files and #include them to your program. Here are the basic structures of your #include preprocessors:
#include <headerfile_name.h>
or
#include "your_file.c"
As you see, when including a headerfile, you will need '<' and '>' and ".h"; while when including your own files, you will need to use double quotation makes and ".c" since your functions are written in C. Through this statement, you will gain access to functions, macros, and global variables declared in the file that you included.

One thing to remember is that when you write your own files to include in your program, you can only declare functions, declare global variables, and use other preprocessors (such as including other files), since that file is being "copied" to your program. Writing your functions in other files are useful because this allowed you to use that function in multiple projects. For example, if we wrote a function that calculates the mean of n numbers, we can put that into a separate file and include it in my program, that way, we can use that function again in another program.

Here is a diagram of file inclusion:

In this image, our main program is called File 1.c, and we included two files: File 2.c and File 4.c. In File 2.c, we also included another file named File 3.c. All of these files will be copied to File 1.c, at the beginning of the program. Therefore, in any functions/macros of this program, you can access any other one, for example, in a function declared in File 3.c, you can access and call another function declared in File 4.c.

Let's look at an example:

/*sort function file named sort_f.c*/
#include <stdlib.h>

void swap(int *a,int *b)
{
  int temp;
  temp=*a;
  *a=*b;
  *b=temp;
}

/*This function sorts list in ascending order*/
void sort(int *list,int length)
{
  int i,j;
  for(i=0;i<length;i++)
   {
     for(j=i+1;j<length;j++)
      {
        if(*(list+i)>*(list+j))
         {
           swap(list+i,list+j);
         }
      }
   }
}

/*Main program file*/
#include <stdio.h>
#include "sort_f.c"

int main()
{
  int numbers [10]={28,93,-22,0,3,-399,93,-23,87,250};
  int i;
  sort(numbers,10);
  for(i=0;i<10;i++)
   {
     printf("%d\t",numbers [i]);
   }
}

-399     -23     -22     0     3     28     87     93     93     250     

The above example demonstrated the basic use of the file inclusion preprocessor of C. Again, functions in <stdlib.h> in our main program file, not just sort_f.c.

#define

The second type of preprocessor in C language is the macro define, or #define preprocessor. When using this preprocessor, you can define a name that substitutes for a value, for example, you can substitute PI for 3.1415926.

This macro definition preprocessor is very useful for several reasons: first, you can substitute long values with a short and clear name; also, when your program gets longer, and you have a value, for example the length of a string, that will be used in multiple spots. By using macro definition defining LENGTH is 200, if you need to change this value while writing the program, instead of changing the value everywhere that you used it, you can just change the definition.

Here is a basic example of the use of this preprocessor:

#include <stdio.h>

/*#define is formated like:
#define NAME (usually capitalized) Replacement Text*/
#define PI 3.14159265358

int main()
{
  int radius;
  double area;
  printf("Enter the radius:");
  scanf("%d",&radius);
  area=PI*(radius*radius);
  printf("The area of a circle with a %d radius is %f\n",radius,area);
}

Enter the radius:The area of a circle with a 2 radius is 12.566370

To use the #define preprocessor, you first need to tell the computer the name for this definition, usually capitalized to differenciate with variables. Then, the replacement text is defined separated from the name by a space (This can be of any type). You can even do something such as: #define PRINT printf, and use printf like this: PRINT("Hello\n"); The computer simply replaces name for that text as the first step of compilation.

It is also possible to declare macros with arguments. For example, you can declare a macro like this:
#define SUM(A,B) A+B
And in that case,
result=SUM(3,5);
Would assign 8 to result. This is especially useful if you use a ?: ternary operator, explained below.

Conditional Expression with Ternary Operator

This is another way you can let the computer make choices other than the if statement in C. You would give the computer an expression and two values. This conditional expression will evaluates to the first value when the expression you give it is true, while the second value when false. The basic structure:
Expression1 ? Value 1 : Value 2;
For example:
greater=(a>b) ? a : b;
This assignment first checks if a is greater than b. If it is, it assigns a to the variable greater; if it is not, it assigns b to greater. (Parenthesis are not required) The conditional expression consists of a expression, a question mark, the first value, colon, and then the second value.

This is useful with macro definition because you can do:
#define GREATER(A,B) (A>B) ? A : B
And when you use GREATER(a_number,another_number) in your program it would give you the greater one.

Conditional Inclusion and Preprocessing

You can also use preprocessors to include a file under certain conditions, just like the if statement, and also execute a block of your program when something is defined. There are three types of conditional preprocessing (#if, #ifdef, and #ifndef). Let's first look at #ifdef.

#include <stdio.h>
#define DEBUG 1

int main()
{
  int a=1,b=2,c=3;
  #ifdef DEBUG
    printf("%d %d %d\n",a,b,c);
  #else
    printf("Debug not defined\n");
  #endif
}

1 2 3

This example demonstrated a basic use of this conditional preprocessor. This type of conditional preprocessor is very similar to the if structure, you can have #ifdef/#if for if(), #elif for else if(), and #else for else. Each block in conditional preprocessing ends at an #elif, #else, or #endif. After #ifdef, separated by a space, is a text string. If that text string is defined, then the body of this conditional preprocessor will be executed. Otherwise, it will skip it. Conditional preprocessing can be useful, as demonstrated above, when you are debugging. You can add printf statements in conditional preprocessors and print them out when DEBUG is defined. When you are finished debugging, you can simply take out the #define DEBUG 1 line.

The second conditional preprocessor similar to #ifdef is the #ifndef preprocessor. It works the same way as #ifdef except that it executes the block when the text string is not defined, and skips it when it is.

The last type is #if. It works similarly to the last two, except that instead of a text string, it accepts an expression (which can contain macros defined or constant values) to be evaluated. Like the if-structure, if it is TRUE or a non-zero value, the body will be executed, else, it will be skipped:

#include <stdio.h>
#define SIZE 8

int main()
{
  #if SIZE==8
    /*Do this*/
  #else
    /*Do that*/
  #endif
}

Examples

Let's look at some examples of macro definition and conditional preprocessors use.

#include <stdio.h>
#define TOF(A) (A ? "TRUE" : "FALSE")
/*This macro calls for an argument A (could be an expression), and if A evaluates to TRUE
or has a non-zero value, then the string TRUE is returned. Otherwise, FALSE is returned*/

int main()
{
  printf("%s\n",TOF(0));
  printf("%s\n",TOF(1));
  printf("%s\n",TOF(5<3));
}

FALSE
TRUE
FALSE

#include <stdio.h>
#define PRINTSTATUS(A,B,C) printf("%d,%d,%d\n",A,B,C);

int main()
{
  int a=1,b=2,c=3;
  PRINTSTATUS(a,b,c);
}

1,2,3

/*Change all letters in a string to lower or upper case depend on the macro CASE*/
#include <stdio.h>
#define CASE 1
/*1 means lowercase, 2 means uppercase*/

int main()
{
  char text [100]="Abacles C Programming Tutorial Preprocessors Lesson\n";
  int i;
  for(i=0;text [i]!='\0';i++)
    {
      #if CASE==1
        if(text [i]>='A' && text [i]<='Z')
	 {
	    text [i]+=32;
	 }
      #elif CASE==2
        if(text [i]>='a' && text [i]<='z')
	 {
	   text [i]-=32;
	 }
      #else
        printf("Error case!");
      #endif
    }
  printf("%s",text);
}

abacles c programming tutorial preprocessors lesson

Practice

Write a function that orders an array of integers in ascending order and save the function in a file. Then, write another program (in a different file) that calls the function. Include the first file in the second one.

Define a macro PI with value 3.14, then, define another AREA allowing an argument to be passed (the radius) and substituting with the value of the area.

Use conditional preprocessing to order an array of numbers in ascending or descending order depending on the macro ORDER's value.

Additional practice on preprocessers:

Activity 16.2

<< Previous TOC Next >>