Scripting

Preprocessor Directives in C

The preprocessor of a compiler acts in the first instance of compilation. In this instance, for example, the compiler inserts the additional code of the functions that is defined in the headers that we insert and call in the code, the defined constants that they will use, and so on.

The preprocessor configures the compilation based on the requirements of the code that we want to compile using certain directives. Although we generally make little use of the preprocessor directives in simple programs, many of them configure the compilation through the headers that we insert. These directives are a very useful resource to ensure the portability and reduce the size of the final program code by eliminating unnecessary fragments during compilation.

In this Linux Ways article, you’ll learn what preprocessor directives in C are all about and how to use them. We’ll look at each of the directives available in C, explain their usefulness in detail, and include a practical example with code and pictures where you can learn how they work and quickly put them into practice.

#Include Directive in the C Language

The #include directive includes the headers in the compilation where the functions that we use, their variables, structures, directives, etc. are declared. When the precompiler encounters this directive, it replaces it with the code that is contained in the specified file. Let’s see the #include syntax:

#define <file.extension>

#define "file.extension"

If the name of the header is between the < > symbols, the precompiler looks for the files in the “includes” of the libraries. While if it is between the quotation marks, it looks in the directory where the main file of “c” that is to be compiled is located. The #include directives must be in the first lines of the code.

How to Use the #Include Directive to Include the Headers in the C Language

In this example, we will see how to use the #include directive to include the “stdio.h” header which defines the printf() function that we will use to print the “hello World” message in the command console. Let’s see the code for this example:

#include <stdio.h>

void main ()

{

printf ("Hello World\n");

}

The following image shows the compilation and execution of this code:

 

Had we not used this directive to include the “stdio.h” header, the compiler would not have found the declaration of the printf() function which leads to the following error, one of the classic errors when compiling with GCC if we fail to include a header:

#Define and #Undef Directives in the C Language

The #define directive is very useful in C and we can find it in the various headers that define the functions. The usefulness of this directive is wide and it is used to:

  1. Define the constants and flags
  2. Define the macro functions
  3. Include the code snippets to the compilation using the “this” and “conditionals” directives

Let’s see the syntax that is used by this directive to declare a constant and assign it with a value:

#define <CONSTANT> <VALUE>

To define a macro function with this directive, we have to use the following syntax:

#define <function prototype> <expression>

The #undef directive has the opposite effect of the #define and is used to remove the definitions that have already been created. Let’s see the syntax of this declaration to undefine a constant:

#undef <CONSTANT>


How to Define a Constant with the #Define Directive in the C Language

In this example, we define a constant with the CONST_1 identifier and assign it with the value of 15. Then, we print its value with the printf() function using the constant as an input argument.

#include <stdio.h>

#define CONST_1 15

void main ()

{

printf ("%i", CONST_1);

}

Let’s see the following image with the compilation and execution of this code that shows the value that is defined to the CONST_1 constant by the #define directive:

Let’s see what happens when we remove the definition of the CONST_1 constant with the #undef directive:

#include <stdio.h>

#define CONST_1 15

#undef CONST_1

void main ()

{

printf ("%i", CONST_1);

}

As the following figure shows, the CONST_1 constant was removed by the #undef directive, so the compiler could not find it and issued an error.

How to Define a Macro Function with the #Define Directive in the C Language

Let’s see an example where we define the conv() macro function with the #define directive which converts its input argument from inches to centimeters:

#include <stdio.h>

#define conv(b) a * 2.54

void main ()

{

float a =2.5;

printf ("%f inches equal to %f centimeters\n", a, conv(a));

}

Let’s see the following image with the compilation and execution of this code:

Conditional Preprocessor Directives

These conditionals include or exclude the code fragments from the compilation based on a previous definition or the result of a relational operation.

These directives allow us to include only the headers, libraries, and fragments that are necessary for a particular compilation which significantly reduces the final size of the program by not including the unnecessary code. Also, the code becomes portable and we can specify the different compilation modes which we can easily define with the #define directive.

In the following, you will find a list of conditional preprocessor directives available in C:

Directive Entry condition
#ifdef <condition> If the condition was previously defined
#ifndef <condition> If the condition was not previously defined
#if <condition> If the condition is true
#elif <condition> If the condition is true and false for #if
#else If the condition result false for #if and #elif
#endif (Delimiter) End of conditional

 

Whenever we open one or a series of conditional directives, we must mark their end with #endif.

The #Ifdef and #Ifndef Conditional Directives in the C Language

The #ifdef and #ifndef directives are conditional directives that depend on a previous definition. Let’s see how the #ifdef directive works:

#define US

#ifdef US

//If US is defined, the compiler will include this code in the compilation.

The #ifndef directive works in the opposite way. It includes the assigned code if the constant of its condition is not defined which, in this case, is US.

#ifndef US

//If US is NOT defined, the compiler will include this code in the compilation.


How to Use the #Ifdef and #Ifndef Directives in C Language

In this example, we will see how we can use the #ifdef and #ifndef conditional directives to make our code portable while creating a lightweight and readable program. To do this, we create a simple application that calculates the circumference of a circle based on a radius that we enter through the console. The interesting thing is that it supports two languages, English (US) and Spanish for Latin America (LATAM) and uses the unit of measurement corresponding to the zone chosen. Let’s see the C code for this program:

#include <stdio.h>

//Here we define which zone we are going to use. To use the LATAM zone uncomment the following line.

//#define LATAM

#include "myheader.h"
void main ()
{
float a, b;
printf ("%s", in);
scanf  ("%f", &a);
printf ("%f (%f %s)\n", a, conv(a), unit_conv);
b=(a*2)*3.1416;
printf (out, b, conv(b), unit_conv);
}

As we can see, in this very simple code, we add the “myheader.h” header which we save in the same directory where we have the “.c” file.

In the “myheader.h” header, we use the #ifdef directive and write the code for LATAM in it. We do the same with the #ifndef directive in which we write the code for US. In each of these two directives, we define a macro function to convert the unit of measurement used in that area, as well as the constant strings that correspond to the language of the selected area. Let’s see the code for this header:

#define MYHEADER_H

#ifdef LATAM
//If LATAM is defined, the compiler will include this code in the compilation.

#define conv(b) b / 2.54
char in[]  = "Zona Latino America\nIngresar radio en centímetros \n";
char unit_conv[]  = "pulgadas";
char out[] = "La circunferencia en pulgadas es %f (%f %s) \nUna pulgada equivale a 2.54cm\n";
#endif

#ifndef LATAM
//If LATAM is NOT defined, the compiler will include this code in the compilation.
#define conv(b) b / 0.393701
char in[]  = "Zone US\nEnter ratio in inches\n";
char unit_conv[]  = "cm";
char out[] = "The circumference in inches is %f (%f %s) \nOne inch is equal to 2.54cm\n";
#endif

If LATAM is not defined in our code, the compiler defaults to compiling what is in the #ifndef directive, that is, the English-language strings. If LATAM is defined, it compiles the code corresponding to the #ifdef directive in which the strings are in Spanish. We insert this definition in the C code before the line that contains the “myheader.h” header. Let’s see how this code is compiled and executed by default:

 

Now, let’s uncomment the LATAM definition of our “main.c” code and compile and run it again:

As we can see, using the conditional directives, we can develop a single code to compile different versions of the same program, omitting unnecessary code that slows down the process and takes up the memory.

The #If, #Elif, and #Else Directives in the C language

These conditional directives include the code fragments in the compilation depending on whether the result of a relational operation between values of predefined constants is true or false. Then, we will see how these directives are applied:

#define LATAM 1
#define FR    2

#define ZONE FR

#if ZONE == LATAM

//If LATAM is defined, the compiler will include this code in the compilation.

#elif ZONE == FR

//If FR is defined, the compiler will include this code in the compilation.

#else

//If LATAM ot FR is not defined, the compiler will include this code in the compilation.

#endif

The preprocessor includes the code that is contained in the conditional statement whose result of the relational operation with the ZONE constant equals to true which, in this case, is the #elif statement with the FR constant.

How to Use the #If, #Elif, and #Else Conditional Directives in the C Language

Now, let’s look at an example where we use these three directives that take the code from the previous example and make some changes to the “myheader.h” file to add the French language to the program.

First, we define the FR, LATAM, and US constants and assign a value to each. Also, in the C code, before inserting the “myheader.h” header, we define the ZONE constant which we will use to select the language of the program by assigning the value of one of the three constants that we just defined. The ZONE constant will be the condition for the conditional directives.

Next, we add the #if, #elif, and #else directives in the header. The code for the FR language is inserted under the #if directive, the LATAM code under #elif, and the US code under the #else directive. This way, we have three compilation versions. But if you want to add more, you can insert as many #elif statements as you want, as long as each has a different constant value in the relational operation.

The #else directive does not perform any relational operations, so US is the default language and can be entered with any value except FR or LATAM. Let’s see the code for the “myheader.h” header:

#ifndef MYHEADER_H
#define MYHEADER_H
//Here we define the constants with the options that we are going to use.
#define US          0
#define FR          1
#define LATAM       2

//Here we define the compilation option for FR.
#if ZONE == FR
#define conv(b) b / 2.54
char in[]  = "Zone France\nEntrez le rayon en pouces\n";
char unit_conv[]  = "cm";
char out[] = "La circonférence en pouces est %f (%f %s) \nUn pouce équivaut à 2,54 cm";

//Here we define the compilation option for LATAM.
#elif ZONE == LATAM
#define conv(b) b / 2.54
char in[]  = "Zona Latino America\nIngresar radio en centímetros \n";
char unit_conv[]  = "pulgadas";
char out[] = "La circunferencia en pulgadas es %f (%f %s) \nUna pulgada equivale a 2.54cm";

//Here we define the compilation option for the USA.
#else
#define conv(b) b / 0.393701
char in[]  = "Zone US\nEnter ratio in inches\n";
char unit_conv[]  = "cm";
char out[] = "The circumference in inches is %f (%f %s)\nOne inch is equal to 2.54cm";

#endif
#endif

To choose in which language we want to compile our program, we just need to define the ZONE constant in the code and assign it with one of the three values (US, FR or LATAM) that are defined in the header. Let’s see the code with the language options that are commented out so that the default zone, which is US, is compiled:

#include <stdio.h>

//Here we define which zone we are going to use.
#define ZONE US
//#define ZONE LATAM
//#define ZONE FR
#include "myheader.h"

void main ()
{
float a, b;
printf ("%s", in);
scanf ("%f", &a);
printf ("%f (%f %s)\n", a, conv(a), unit_conv);
b=(a*2)*3.1416;
printf (out, b, conv(b), unit_conv);
}

Let’s see the execution of this code with its default US compilation:

 

Now, we comment out the ZONE US definition and uncomment the ZONE LATAM definition, compile them, and run them again.

Now, we comment out the ZONE definition and uncomment the ZONE FR definition, compile them, and run them again.

In this way, we can use the #if, #elif, and #else conditional directives to specify the various compilation options by simply specifying the value of a constant.

 

#Error and #Warning Directives in the C Language

These directives are used to send error or warning messages to the compiler output. These directives are usually used in headers within error detection conditionals. The message to be displayed in the compiler output must be in the text form and not a string. Let’s see an example of the use of these two directives:

#include <stdio.h>

void main ()

{

#error This is a test of the error directive.

#warning This is a test of the warning directive.

}

In the following figure, we will see the compilation of this code with the error and warning messages it contains:

 

Conclusion

In this Linux Ways article, we explained to you what the preprocessor directives are all about and what their function is when compiling the code. In this article, we looked at each of these directives and showed you their syntax and usage. To help you quickly implement the use of preprocessor directives, we included a practical example for each of them, with code fragments and images, so that you can see the potential of these directives for code portability and optimization of the final program.

Similar Posts