Preprocessor Directives and Macros in C Programming

June 27, 2018 | 1 Tags | 0 Comment

Preprocessor Directives and Macros in C Programming

The C Preprocessor, often known as CPP is a macro processor that is used by the C compiler to transform your program before compilation. It is called a macro processor because it allows you to define macros.

Preprocessor directives begin with a # symbol. Preprocessor directives are used to define and replace tokens in the text and also used to insert the contents of other files into the source file. In C program, collection of all keywords, identifiers, operators, special symbols, constants, strings and data values are called as tokens.

#define Directive

This directive defines macros. There are two types of macros: those with parameters and those without parameters.

Example of macro without parameters

 // Program to display the area of circle
 #include <stdio.h>
 #define PI 3.1415
 int main() {
    float radius, area;

    printf("Enter the radius of circle: ");
    scanf("%d", &radius);

    area = PI*radius*radius;
    printf("Area of circle = %.2f\n", area);

    return 0;
 }

In pre-processing stage, PI will be replaced by 3.14, so PI*radius*radius will be 3.14*radius*radius.

Example of macro with parameters

 // Program to display the area of circle
 #include <stdio.h>
 #define PI 3.1415
 #define circleArea(r) (PI*r*r)
 int main() {
    int radius;
    float area;

    printf("Enter the radius of circle: ");
    scanf("%d", &radius);

    area = circleArea(radius);
    printf("Area of circle = %.2f\n", area);

    return 0;
 }

In pre-processing stage, circleArea(radius) will be replaced by 3.14*r*r.

Macros are often used to execute a sequence of multiple statements as a group. When multiple statements are used in a macro, they should be bound in a do-while loop syntactically, so the macro can appear safely inside a statement block that doesn’t use braces (when a statement block uses braces, then multiple statements in a macro will expand correctly event without a do-while loop).

In this noncompliant code example, the macro contains multiple unbound statements

 #include <stdio.h>
 #define SWAP(x, y) \
   tmp = x; \
   x = y; \
   y = tmp

 int main() {
    int x, y, z, tmp;
    if (z == 0)
      SWAP(x, y);

    return 0;
 }

It will expand to the following, which is certainly not what the programmer intended

 #include <stdio.h>
 #define SWAP(x, y) \
   tmp = x; \
   x = y; \
   y = tmp

 int main() {
    int x, y, z, tmp;
    if (z == 0)
      tmp = x;
    x = y;
    y = tmp;

    return 0;
 }

Next, in this noncompliant code example, the macro contains multiple statements bounded with curly brackets

 #include <stdio.h>
 #define SWAP(x, y) { tmp = (x); (x) = (y); (y) = tmp; }

 int main() {
    int x, y, z, tmp;
    if (x > y)
      SWAP(x, y);  /* Branch 1 */
     else
       do_something();  /* Branch 2 */

    return 0;
 }

the macro will fail to expand and will be interpreted as an if statement with only one branch

 #include <stdio.h>
 #define SWAP(x, y) { tmp = (x); (x) = (y); (y) = tmp; }

 int main() {
    int x, y, z, tmp;
    if (x > y) { /* Single-branch if-statement!!! */
      tmp = x;   /* The one and only branch consists */
      x = y;     /* of the block. */
      y = tmp;
    }
    ;            /* Empty statement */
    else         /* ERROR!!! "parse error before else" */
      do_something();

    return 0;
 }

So what is the compliant solution for this case? That is wrapping the macro inside a do-while loop.

 #include <stdio.h>
 #define SWAP(x, y) \
   do { \
     tmp = (x); \
     (x) = (y); \
     (y) = tmp; } \
   while (0)

 int main() {
    int x, y, z, tmp;
    if (x > y)
      SWAP(x, y);
    else
      do_something();

    return 0;
 }

The do-while will always be executed exactly once.

#include Directive

The #include directive is used to include header files to a C program. #include directive has two variants: #include <file> and #include "file".

#include <file> searches for a file named file in a defined places by compiler. GCC compiler looks in several different places for headers. You can see where it looks for header files by using this command

 cpp -v

Include directories

#include "file" searches for a file named file first in the directory containing the current file, if it’s still not found then it continues in the defined places by compiler.

If you want to add additional directories to the search path, you can specify multiple -Idir options.

If a header file happens to be included twice, the compiler will process its content twice. This is very likely to cause error. The standard wy to prevent this is to enclose the entire real contents of the file in a conditional, like this

 /* File foo.  */
 #ifndef FILE_FOO_SEEN
 #define FILE_FOO_SEEN

 the entire file

 #endif /* !FILE_FOO_SEEN */
#ifdef Directive

The #ifdef directive checks for the existence of macro defenitions.

The following example defines MAX_LEN to be 75 if EXTENDED is defined for the preprocessor. Otherwise, MAX_LEN is defined to be 50.

 #ifdef EXTENDED
 #   define MAX_LEN 75
 #else
 #   define MAX_LEN 50
 #endif
#ifndef Directive

The #ifndef directive checks whether a macro is not defined.

The following example defines MAX_LEN to be 50 if EXTENDED is not defined for the preprocessor. Otherwise, MAX_LEN is defined to be 75.

 #ifndef EXTENDED
 #   define MAX_LEN 50
 #else
 #   define MAX_LEN 75
 #endif
#if Directive

The #if directive allows you to test the value of an arithmetic expression, rather than the mere existence of one macro.

The expression of #if directive may contain

  • integer constants
  • character constants
  • arithmetic operators for addition, subtraction, multiplication, division, bitwise operations, shifts, comparisons, and logical operations
  • Uses of the defined operator, which lets you check whether macros are defined
  • Identifiers that are not macros is considered false (zero), function-like macros used without their function call parentheses are also treated as false (zero)
 #if DLEVEL > 5
     #define SIGNAL  1
     #if STACKUSE == 1
         #define STACK   200
     #else
         #define STACK   100
     #endif
 #else
     #define SIGNAL  0
     #if STACKUSE == 1
         #define STACK   100
     #else
         #define STACK   50
     #endif
 #endif
 #if DLEVEL == 0
     #define STACK 0
 #elif DLEVEL == 1
     #define STACK 100
 #elif DLEVEL > 5
     display( debugptr );
 #else
     #define STACK 200
 #endif
#undef Directive

If a macro ceases to be useful, it may be undefined with #undef directive.

 #undef MAX_LEN
#line Directive

The #line directive supplies line numbers for compiler messages. It causes the compiler to view the line number of the next source line as the specified number.

You can use #line directives to make the compiler provide more meaningful error messages.

 #include <stdio.h>
 #define LINE200 200

 int main(void)
 {
    func_1();
    func_2();
 }

 #line 100
 func_1()
 {
    printf("Func_1 - the current line number is %d\n",__LINE__);
 }

 #line LINE200
 func_2()
 {
    printf("Func_2 - the current line number is %d\n",__LINE__);
 }
#error Directive

The #error directive causes the preprocessor to generate an error message and causes the compilation to fail.

 #define BUFFER_SIZE 255

 #if BUFFER_SIZE < 256
 #error "BUFFER_SIZE is too small."
 #endif
#warning Directive

The #warning directive is like #error, but causes the preprocessor to issue a warning and continue preprocessing.

# Operator

The # operator converts a parameter of a function-like macro into a character string literal. For example, if macro ABC is defined using the following directive:

 #define ABC(x)   #x

all subsequent invocations of the macro ABC would be expanded into a character string literal containing the argument passed to ABC. ABC(100) would be expanded into a character string "100" and ABC(Hello World) would be expanded into "Hello World".

The # operator rules in a function-like macro

  • white-space characters that appear after or before the argument passed to the macro are deleted
  • multiple white-space characters embedded within the argument passed to the macro are replaced by a single space character
  • if the argument passed to the macro contains a string literal and if a backslash \ character appears within the literal, a second \ character is inserted before the original \ when the macro is expanded
  • if the argument passed to the macro contains a " (double quotation mark) character, a \ is inserted before the " when the macro is expanded
  • the conversion of an argument into a string literal occurs before macro expansion on that argument
  • if more than one # operator appears in the replacement list of a macro definition, the order of evaluation of the operators is not defined
  • if the result of the macro expansion is not a valid character string literal, the behaviour is undefined
#define STR(x)        #x
#define XSTR(x)       STR(x)
#define ONE           1
Invocation Result of Macro Expansion
STR(\n "\n" '\n') "\n \"\\n\" '\\n'"
STR(ONE) "ONE"
XSTR(ONE) "1"
XSTR("hello") "\"hello\""


null directive (#)

The null directive performs no action. It consists of a single # on a line of its own.

In the following example, if MINVAL is a defined macro name, no action is performed. If MINVAL is not defined, it is defined 1.

#ifdef MINVAL
  #
#else
  #define MINVAL 1
#endif
## Operator

The ## (double number sign) operator concatenates two tokens in a macro invocation (text and/or arguments) given in a macro definition.

The ## (double number sign) operator rules

  • concatenation take place before any macros in arguments are expanded
  • if more than one ## operator appears in the replacement list of a macro definition, the order of evaluation of the operators is not defined
#define ArgArg(x, y)          x##y
#define ArgText(x)            x##TEXT
#define TextArg(x)            TEXT##x
#define TextText              TEXT##text
#define Jitter                1
#define bug                   2
#define Jitterbug             3
Invocation Result of Macro Expansion
ArgArg(lady, bug) ladybug
ArgText(con) conTEXT
TextArg(book) TEXTbook
TextText TEXTtext
ArgArg(Jitter, bug) 3


Predefined Macros

There are some predefined macros in C programming.

Predefined Macro Description
__DATE__ String containing the current date
__FILE__ String containing the file name
__LINE__ Integer representing the current line number
__TIME__ String containing the current time
__STDC__ The integer 1 indicates that the C compiler supports the ISO C standard


#include <stdio.h>
int main()
{
   printf("Current time: %s\n",__TIME__);
   printf("Current time: %s\n",__DATE__);
   printf("Current time: %s\n",__FILE__);
   printf("Current time: %d\n",__LINE__);
   printf("Current time: %d\n",__STDC__);

   return 0;
}
C
Samuel Yang image
Samuel Yang

If you like this tutorial, you can support me

Donate Now