Road to C Programmer #11 - Macros

Last Edited: 8/14/2024

The blog introduces macros in C.

C Macros

Object-like Macros

In the first article, I briefly mentioned an alternative method of defining a constant, #define, which is an example of object-like macros.

# define PI 3.14
 
int main() {
    int radius = 2;
    printf("Area: %.3f\n", radius*radius*PI); // => Area: 12.560
 
    return 0;
}

As I discussed before, the preprocessor will replace all occurrences of the macro “PI” with 3.14 before compilation. Macros are a way of creating a constant without explicit type declaration, which is not usually considered good practice but is sometimes necessary.

Function-like Macros

In addition to defining a constant with macros, you can also define a function* using macros like the following:

# define PI 3.14
# define area(r) r*r*3.14
 
int main() {
    int radius = 2;
    printf("Area: %.3f\n", area(radius)); // => Area: 12.560
 
    double dRadius = 2.5;
    printf("Area: %.3f\n", area(dRadius)); // => Area: 19.625
 
    return 0;
}

Just as object-like macros do not care about types, function-like macros also do not care about input types. Since macros are just text replacements, they do not have to store input parameters in memory like a normal function. Function-like macros can also take multiple arguments:

# define findMax(array, length) ({ \
    typeof(array[0]) curMax = array[0]; \
    for (int i = 0; i < length; i++) { \
        if (array[i] > curMax) { \
            curMax = array[i]; \
        } \
    } \
    curMax; \
})
 
int main() {
    int array1[4] = {5,7,9,1};
    printf("Max: %d\n", findMax(array1, 4)); // => Max: 9
 
    double array2[4] = {0.33, 1.94, 8.34, 3.14};
    printf("Max: %.2f\n", findMax(array2, 4)); // => Max: 8.34
 
    return 0;
}

When using macros, we need to be careful that they do not have return statements, as macros are just text replacements.

Conditional Compilation Directives

When we want to use certain functions only for debugging, we can define a DEBUG_MODE object-like macro that can take a value of either 0 or 1, and use conditional compilation directives like the following:

# define DEBUG_MODE 1
 
int main() {
	
    #if DEBUG_MODE == 1
	printf("DEBUG_MODE! \n");
    #elif DEBUG_MODE == 0
	printf("NOT DEBUG_MODE! \n");
    #else
	printf("???\n");
    #endif
 
    return 0;
}

You can also use defined(), #ifdef, or #ifndef to check if object-like macro named DEBUG_MODE is defined or not.

# define DEBUG_MODE
 
int main() {
	
    #if defined(DEBUG_MODE)
	printf("DEBUG_MODE! \n");
    #endif
 
    #ifdef DEBUG_MODE
	printf("DEBUG_MODE! \n");
    #else
	printf("NOT DEBUG_MODE! \n");
    #endif
 
    #ifndef DEBUG_MODE
	printf("NOT DEBUG_MODE! \n");
    #endif
 
    return 0;
}

The above approach is simpler than allowing DEBUG_MODE to take on specific values. Conditional compilation directives can also be used to set different values to object-like macros depending on conditions.

Exercises

From this article, there will be an exercise section where you can test your understanding of the material introduced in the article. I highly recommend solving these questions by yourself after reading the main part of the article. You can click on each question to see its answer.

Resources