C: Macro Function vs Regular Function vs Inline Functions

If you have some experience with C programming and you have had the opportunity to look at some library code then you must have seen something like this

#include<stdio.h>
#define ADD(x,y) (x) + (y)

int main()
{
    printf("1 + 2 = %d", ADD(1,2));
    return 0;
}

The above construct can be called as a macro function or a smart macro.

If this code is executed we would get the same result as if ADD is a regular function like in the code listing below.

#include<stdio.h>
int add (int x, int y)
{
    return x + y;
}
int main()
{
    printf("1 + 2 = %d", add(1,2));
    return 0;
}

When I was looking at these macro functions I had several questions pop up in my head.

  • Are macros better than normal functions?
  • If so why don’t we use macro functions everywhere?
  • What is the reason behind using macros when there is a proper way to do it (using regular functions)?

So I did some research on this and in this article, I have summarized the pros and cons of using macro functions (Yes, there are some pros!). Let’s look at the various scenarios where macros are more useful, the possible pitfalls of using macros and when to avoid them and just use regular functions.

Recalling What We Learned About Macros

When we learned C programming, the books taught us that macros must be used in the place of literal values.

#include<stdio.h>
#define PI 3.1415
int main()
{
    printf("The value of PI is %f", PI);
    return 0;
}

The C preprocessor will do textual substitutions to make the code look like this

#include<stdio.h>

int main()
{
    printf("The value of PI is %f", 3.1415);
    return 0;
}

This helps to achieve 2 things

  1. Code readability (literal values must have some meaning behind them, else to the reader they are just magic numbers )
  2. Less memory usage as compared to using PI as a variable

Over the years, programmers started using this text substitution feature of the C language and made code similar to Listing 1 seen at the beginning of this article, where entire functions were written using macros.

Let’s have a look at 3 criteria: Readability, Performance, and Code Size to see which one is better, regular functions or macro functions.

Criteria#1: Readability

We have already seen above that macros help in readability when it comes to literal values. Now Let’s look at the readability factor when it comes to functions to see which one is better, Macros Functions or Regular Functions.

Macro Function

Consider the code below.

#include<stdio.h>
#define MEDIAN(a,b,c) ((a > b) \
                     ? (((b > c) ? b : (a > c) ? c : a) ) \
                     : ((b > c) ? (a > c) ? a : c : b))
int main()
{
    printf("median of 2,48,22 = %d", MEDIAN(2, 48, 22));
    return 0;
}

The “\” symbol is used to extend a macro to multiple lines

The code above calculates the Median value of 3 given numbers. If the condition (a>b) is true the second line is executed. If it is false the 3rd line is executed. This macro is written using the tertiary operator “?:” which makes the macro to resolve into a single number.

The code will output something like this

median of 2,48,22 = 22

Regular Function

This “hard to follow “code, which will take some effort to read and digest. It can be rewritten in a more readable manner using regular functions like this

#include<stdio.h>

int median(int x, int y, int z) 
{
    int median = 0;
    if (x > y) {
        if (y > z) {
            median = y;
        }
        else {
            if (x > z) {
                median = z;
            }
            else {
                median = x;
            }
        }
    }
    else {
        if (y > z) {
            if (x > y) {
                median = x;
            }
            else {
                median = z;
            }
        }
        else {
            median = y;
        }
    }
    return median;
}

int main()
{
    printf("median of 2,48,22 = %d", median(2, 48, 22));
    return 0;
}

Even though we are sacrificing more number of lines for the same logic, at least the code is more readable, which I believe is a win for the normal functions.

Criteria #2 Performance

Let’s take another look at the same example above and see which one wins can run faster.

Regular Function

In case of using regular functions the flow of the code is going to be something like this

Execution while using regular functions
Execution while using regular functions

The above figure shows the execution of the code. I hope you are familiar with how the execution is carried out when using functions in C.

The short version is the local variables are pushed onto the stack whenever execution leaves the main function and the local variables are popped back from the stack when the execution returns back to the main function. This process, that is associated with all function calls causes an overhead. In other words, when we use functions the processor has to do some extra work to switch between them.

Macro Function

In the case of macros, the pre-processor is going to substitute the text like this.

printf("median of 2,48,22 = %d", (2 > 48) ?  (((48 > 22) ? 48 : (2 > 22) ? 22 : 2) ) : ((48 > 22) ? (2 > 22) ? 2 : 22 : 48));

So the execution of the code is not going to leave the printf line.

The overall code execution is going to look like this.

Execution when using macro functions
Execution when using macro functions

As you can see in the above picture, the overhead related to function switching has reduced significantly. 

If this MEDIAN macro is going to be used several times in the code then this macro is going to give us a significant boost in performance.

Hence the macro functions win the performance round!

Criteria #3: Code Size

Till now we have used the macro and regular function only once in our code.  For the sake of argument assume we need to use these functions 200 times in our code, how will it affect the code size?

Macro Function

As we have seen above, the pre-processor is going to make text substitutions when it comes to macros.  Consider the code snippet below where the macro is being called 200 times, which is pretty normal in any large codebase.

    printf("MEDIAN of a,b,c = %d", MEDIAN(a, b, c)); // 1st time
    printf("MEDIAN of a,b,c = %d", MEDIAN(a, b, c)); // 2nd time
    printf("MEDIAN of a,b,c = %d", MEDIAN(a, b, c)); // 3rd time
    printf("MEDIAN of a,b,c = %d", MEDIAN(a, b, c)); // 4th time
    .
    .
    .
    printf("MEDIAN of a,b,c = %ld", MEDIAN(a, b, c)); //200th time

So each line is going to be replaced into something like this

 printf("median of a,b,c = %d", (a > b) ?  (((b > c) ? b : (a > c) ? c : a) ) : ((b > c) ? (a > c) ? a : c : b));

Now compiling the above code with gcc compiler on ubuntu results in an executable file of size 25016 bytes

Regular Function

In case of using regular functions, no matter the number of times we use it in our code, the size is not going to change by much. This is because when the compiler takes our code in C and translates it into ASSEMBLY language, it is going to substitute all the function calls using JUMP instructions to that particular function.

So replacing the MEDIAN macro using median function resulted in a file of size 16824 bytes As you can see our executable reduced by over 30% in size when using regular functions.

So the regular function wins the code size round.

So to summarize our results

Readability: Regular functions

Performance: Macro Functions

Code size: Regular functions

At this point you are probably wondering, why not use inline function and let the compiler take care of code substitution. This way it is easier to get both a good performing code and a readable one. We will visit the inline functions vs macro functions topic in the next part of this article as this one is getting too long already.

You can find the next part of this article in this link.

I hope you enjoyed reading this article and got some value from it.

Feel free to share it with your friends and colleagues!

If you have any questions or suggestions you can also email us or contact us through this link!

Related Articles

Complete Guide On Using Doxygen To Document C Source Code..!!

How To Use The Power Of Comments In Your Code The Right Way?

Photo of author
Editor
Balaji Gunasekaran
Balaji Gunasekaran is a Senior Software Engineer with a Master of Science degree in Mechatronics and a bachelor’s degree in Electrical and Electronics Engineering. He loves to write about tech and has written more than 300 articles. He has also published the book “Cracking the Embedded Software Engineering Interview”. You can follow him on LinkedIn

Comments are closed.