#define Preprocessor Directive


In C programming language, a #define directive is a preprocessor directive used to create a symbolic constant or a macro. The #define directive defines a symbolic name or identifier and its corresponding value, which can be used throughout the program.

This directive is used to define a macro. C macros are a powerful feature of the C programming language that allows you to define a sequence of instructions that can be reused throughout your code.

The General Syntax is -

macro #define in c

Declaration of #define macro in C/C++

1. Here macro_name is any valid C identifier, and it is generally taken in capital letters to distinguish it from other variables.

2. The macro_expansion can be any text. A space is necessary between the macro name and macro expansion.

3. The C preprocessor replaces all the occurrences of macro_name with the macro_expansion.

 #define macro_name macro_expansion 

For example-

#deflne TRUE 1

#define FALSE 0

#define PI 3.14159265

#define MAX 100

C preprocessor searches for macro_name in the C source code and replaces it with the macro_expansion. For example, wherever the macro name TRUE appears in the code, it is replaced by 1.

These types of constants are known as symbolic constants.

Constants are values that remain the same throughout the program. They make the program easier to read and understand.

For instance, instead of using the numerical value 3.14, it's preferable to use the name PI. This is because if we want to change the value of PI later, we only need to modify it in one place. If we didn't define PI as a constant, we would have to change every occurrence of 3.14 in the program to the new value of PI. This could be time-consuming and error-prone.

We have already used this concept to establish the size of an array. For example, if we want to increase the MAX_SIZE from 100 to 1000, we only need to modify the #define directive.

The main advantage of using macros is that they can simplify your code by reducing the amount of repetitive code you need to write.

We can also define any string constant in the place of macro_expansion.

#include <stdio.h>#define MYSTRING "Hello, This is a constant string."

int main() {
   printf(MYSTRING);
   return 0;
}

Macros with Arguments

The #define directive can also be used to define macros with arguments. The general syntax is-

#define macro_name(arg 1, arg2, ......) macro_expansion

Here argl, arg2 .... are the fonnal arguments. The macro_name is replaced with the macro_expansion and the formal arguments are replaced by the corresponding actual arguments supplied in the macro call. So the macro_expansion will be different for different actual arguments.

For example, suppose we define these two macros-

#define SUM(x, y) ( (x) + (y) )

#define PROD(x, y) ( (x) * (y) )

Now suppose we have these two statements in our program

s= SUM(5, 6);

p = PROD(m, n);

After passing through the preprocessor these statements would be expanded as

s= ( (5) + (6) );)

p = ( (m) * (n) );)

Since this is just a replacement of text, the arguments can be of any data type.

  #include <stdio.h> 
#define SUM(x,y) ((x) + (y))
#define SQUARE(e) ((e)*(e))
#define DECREMENT_BY_1(y) ((y--))

int main() {
   int  m = 10, n = 20;
   int  sum = SUM(a,b);
   int val_dec_by_1 = DECREMENT_BY_1(m);

  printf("sum = %d, square = %d, dec_by_1 = %d", sum, SQUARE(10),val_dec_by_1);
   return 0;
}

OUTPUT: sum = 30, square = 100, dec_by_1 = 9

This code defines three macros using the #define preprocessor directive: SUM(x,y), SQUARE(e), and DECREMENT_BY_1(y).

The SUM macro takes two arguments x and y and returns their sum.

The SQUARE macro takes a single argument e and returns its square (i.e., e * e).

The DECREMENT_BY_1 macro takes a single argument y, decrements it by one, and returns the new value of y.

Let us see some more examples of macros with arguments -

#define SQUARE(x) ( (x)*(x) )

#define MAX( x, y ) ( (x) > (y) ? (x) : (y) )

#define MIN( x, y ) ( (x) < (y) ? (x) : (y)

#define ISLOVvER(c) ( c >= 97 && c <= 122 )

#define ISUPPER(c) ( c >= 65 && c <= 90 )

#define TOUPPER(c) ( (c) + 'A' - 'a' )

#define ISLEAP(y) ( (y%400 = = 0) II ( y%100!=0 && y%4 = = 0) )

#define BLANK_LINES(n) { int i; fore i = 0; i < n; i++ ) printf("\n"); }

#define CIRCLE_AREA(rad) ( 3.14 * (rad) *(rad) )

Using one macro inside another macro [Nesting in Macros]

In the context of C programming, "nesting in macros" refers to the practice of defining a macro that calls another macro within its definition.

#define SQUARE(x) (x * x)

#define CUBE(x) (SQUARE(x) * x)

In this example, we have defined two macros, SQUARE and CUBE.

The SQUARE macro takes a single argument, x, and returns its square.

The CUBE macro takes a single argument, x, and returns its cube.

However, notice that the CUBE macro calls the SQUARE macro within its definition, effectively "nesting" the SQUARE macro within the CUBE macro.

Let's take this code example -

#include <stdio.h>

#define IS_LOWER(c) (c >= 97 && c <= 122)
#define IS_UPPER(c) (c >= 65 && c <= 90)
#define IS_ALPHA(c) (IS_LOWER(c) || IS_UPPER(c))
#define IS_NUM(c) ((c) >= 48 && (c) <= 57)
#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c))

int main() {
    char ch;
    printf("Enter a character:\n");
    scanf("%c", &ch);

    if (IS_ALPHANUM(ch)) {
        printf("%c is an alphanumeric character.\n", ch);
    } else {
         printf("%c is not an alphanumeric character.\n", ch);
    }

    return 0;
}

This code is a C program that checks whether a given character entered by the user is an alphanumeric character or not.

It defines several macro functions using #define preprocessor directives, which are expanded by the C preprocessor before the program is compiled.

The macros defined in this program are:

  • IS_LOWER(c)

    Returns 1 (true) if the character c is a lowercase letter, and 0 (false) otherwise. This macro uses the ASCII values for lowercase letters, which range from 97 ('a') to 122 ('z').

  • IS_UPPER(c)

    Returns 1 (true) if the character c is an uppercase letter, and 0 (false) otherwise. This macro uses the ASCII values for uppercase letters, which range from 65 ('A') to 90 ('Z').

  • IS_ALPHA(c)

    Returns 1 (true) if the character c is an alphabetic character (either lowercase or uppercase), and 0 (false) otherwise. This macro combines the IS_LOWER and IS_UPPER macros using the logical OR operator ||.

  • IS_NUM(c)

    Returns 1 (true) if the character c is a decimal digit (0-9), and 0 (false) otherwise. This macro uses the ASCII values for digits, which range from 48 ('0') to 57 ('9').

  • IS_ALPHANUM(c)

    Returns 1 (true) if the character c is either an alphabetic character or a digit, and 0 (false) otherwise. This macro combines the IS_ALPHA and IS_NUM macros using the logical OR operator ||.

In the main() function, the program prompts the user to enter a character, reads the input using scanf(), and assigns the input to the variable ch.

It then uses the IS_ALPHANUM macro to check whether ch is an alphanumeric character and prints the appropriate message using printf() based on the result of the check.

Generic Functions or Macro Functions

Generic functions are a powerful feature of the preprocessor that allows us to define macros to generate functions for different data types./p>

Instead of writing separate functions for each data type, we can use a macro that takes the function name and data type as arguments and generates a function definition specific to that data type. This way, we can avoid writing redundant code.

contact_support While C doesn't have built-in support for generic functions, the preprocessor can be used to simulate generic functions using macros. This technique is commonly known as "generic programming" or "template metaprogramming.

Although the concept of generic functions may seem confusing at first, we can easily understand it with the help of a sample program that demonstrates how to define and use them.

#include <stdio.h>

#define MAX(FNAME, DTYPE) \
    DTYPE FNAME(DTYPE X, DTYPE Y) { \
        return X > Y ? X : Y; \
    }

MAX(max_int, int)
MAX(max_float, float)
MAX(max_double, double)

int main() {
    int p;
    float q;
    double r;

    p = max_int(3, 9);
    q = max_float(7.4, 5.7);
    r = max_double(12.34, 13.54);

    printf("p = %d, q = %.2f, r = %.2lf\n", p, q, r);
    return 0;
}

Output: p = 9, q = 7.40, r = 13.54

The three macro calls written just before main( ) are expanded as -

MAX(max_int, int)
arrow_forward
 int max_int (int X, int Y)
 {
     return X>Y? X : Y;
 }
MAX(max_float, float)
arrow_forward
float max_float (float X, float Y)
{
return X>Y? X : Y;
}
MAX(max_double, double)
arrow_forward
double max_double (double X, double Y)
{
return X>Y? X : Y;
}

This code defines a macro called MAX that takes two arguments: the function name and the data type.

It generates a function definition that takes two arguments of the same data type and returns the maximum of the two.

The MAX macro is used to define three functions: max_int, max_float, and max_double. These functions return the maximum of two integers, two floats, and two doubles, respectively.

Problems with Macros

You may be wondering why there are so many parentheses used in the macro expansion.

The reason for this is that the preprocessor replaces the formal argument with the actual argument, and without proper use of parentheses, the resulting expression may not be what we expect.

This can cause problems in our code. Let's take a look at some of the potential issues that can arise when using macros, and how we can avoid them.

Unexpected results due to operator precedence

#define SQUARE(x) x * x

int main() {
    int a = 2, b = 3;
    int result = SQUARE(a + b); // should be 25, but is evaluated as 8
    return 0;
}

In this example, the SQUARE macro is defined to multiply its argument by itself.

When the macro is used with the expression (a + b), which contains addition, the resulting expression is evaluated as 2 + 3 * 2 + 3, because multiplication has higher precedence than addition.

To avoid this problem, parentheses must be used to enforce the correct order of operations:

#define SQUARE(x) ((x) * (x))

Lack of type safety

#define MAX(x, y) ((x) > (y) ? (x) : (y))

int main() {
    char a = 'a';
    int b = 2;
    int result = MAX(a, b); // result is 'a', not 2 as expected
    return 0;
}

In this example, the MAX macro is defined to return the larger of two arguments.

When the macro is used with arguments of different types (char and int), the result is not what we expect.

To avoid this problem, we can cast the arguments to the correct type before comparing them:

#define MAX(x, y) ((x) > (y) ? (x) : (y))

int main() {
    char a = 'a';
    int b = 2;
    int result = MAX((int)a, b); // result is 2 as expected
    return 0;
}

Name collisions

#define PI 3.14159

int main() {
    double PI = 3.14;
    double circumference = 2 * PI * radius; // uses the wrong value of PI
    return 0;
}

In this example, the macro PI is defined globally, but the variable PI is also defined locally in the main function.

When the variable PI is used in the expression for circumference, it uses the wrong value of PI.

To avoid this problem, we can use a different naming convention for macros (such as all caps) to distinguish them from variables:

#define PI_MACRO 3.14159

int main() {
    double PI = 3.14;
    double circumference = 2 * PI_MACRO * radius; // uses the correct value of PI
    return 0;
}

Difficulty with debugging

#define DEBUG printf("line %d\n", __LINE__)

int main() {
    int x = 5;
    DEBUG; // prints "line 5" but doesn't show up in the compiled binary
    return 0;
}

In this example, the DEBUG macro is defined to print the line number where it is called.

However, because macros are expanded by the preprocessor before the code is compiled, the resulting printf statement does not appear in the compiled binary, making it difficult to diagnose problems.

To avoid this problem, we can use a function instead of a macro:

void debug(int line) {
    printf("line %d\n", line);
}

int main() {
    int x = 5;
    debug(__LINE__); // prints "line 7"
    return 0;
}

Functions vs. Macros

Functions and macros are two mechanisms for performing similar tasks in C programming. A macro is expanded into inline code, and the text of the macro is inserted into the code for each macro call.

On the other hand, a function is written only once in the code, regardless of how many times it is called.

Macros make the code longer, and the compilation time increases, while functions make the code smaller.

Functions take some time for passing arguments and returning values, which slows down the execution of the program. On the other hand, macros save time as they don't have to pass arguments and return values. Macros are fast but occupy more memory due to the duplicity of code, while functions take less memory but are slower.

If the macro is small, it is better to use a macro, but if it is large and called many times, then it is better to use a function since it may increase the size of the program considerably. Macros can be useful in improving the execution speed, for example, the macros getchar() and putchar() in file stdio.h.

Macros can be used with arguments of different types, making them more versatile, but they lack type checking. Functions perform type checking, so separate functions have to be written for each data type. The lack of type checking facility in macros makes them more error-prone.

The choice between using a macro or a function depends on the memory available, the requirement, and the nature of the task to be performed.

Loading...

Search