C: Macro Functions vs Regular Functions vs Inline Functions Part#2

So just to recap what we saw in part 1 of this blog Regular functions are better at Readability and Code size while Macro functions are better at code performance.

In this part let’s have a look at inline functions and see how and where to use them and how good of a replacement to a macro function they are.

Inline Functions

This is the last piece of the puzzle. Inline functions originally came in C++ and later in c99 update, it was added to the C programming language. So now in C, we have the ability to use the“inline”  keyword.

What are inline functions? The inline keyword can be used to tell the compiler that we wish to keep the function logic inline so that we can skip the overhead of a function call. In other words, Inline functions are supposed to behave like macros and replace the function calls with the actual statements.

Let’s take the same median example that we have been dealing with.

#include<stdio.h>

inline 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()
{
    int x = 10;
    int y = 50;
    int z = 20;
    printf("MEDIAN of x,y,z = %d", median(x, y, z));
    return 0;
}

Here all I did is add the word “inline” in front of our function declaration to tell the compiler that we need to inline this function the same way as we can do with a macro.

Thus we have achieved both readability and performance by using inline functions!

Inline functions and code size

The inline function can sometimes increase the code size and sometimes reduce it. I guess you already understand the scenario where it increases code size, which is a result of code substitution. Let’s look at the scenario where the inline function can actually reduce code size.

Assume we have a very small function, which when translated to assembly language will give us just 4 lines of assembly code.

Also, assume in our system the overhead of a function call is 3 instructions to push onto the stack and 3 instructions to pop it back from the stack so a total of 6 assembly instructions for the overheads associated with function calls.

Now if we don’t inline that function then it will take a total of 10 assembly instructions to execute, but if we inline it we can do it in just 4 assembly lines..! Thereby reducing the code size and improving performance at the same time. To summarise, if the function logic is small enough, it’s better to make it inline to avoid function switching overhead.

So is the problem solved? Do we have the best solution/replacement for macro functions? Not quite yet! There are places where regular functions cannot replace macro functions..! Let’s see what those places are

Macro specific situations

So what is the need for these macro functions when we can use inline functions?

Situation#1: Generic functions

Macros are not type-safe since its just text substitutions, and this feature/flaw can be used to our advantage!

Have a look at this example where we have a generic function that can take in integers, floats, chars, doubles or even pointers and returns the maximum of the 2 numbers.

#include<stdio.h>

#define MAX(a,b) (((a) > (b)) ? (a) : (b)) 
int main()
{
    int x = 10;
    int y = 50;
    printf("MAX of x,y = %d\n",MAX(x,y));
    double a = 11.23;
    double b = 8.47;
    printf("MAX of a,b = %f\n",MAX(a,b));
    return 0;
}

If you want to implement the same logic using inline function your code needs to have separate implementations for doubles, int, chars, pointers, etc. The code below shows this for ints and doubles.

#include<stdio.h>

inline int max_int(int a, int b)
{
    return (a > b)?a:b;
}

inline double max_double(double a, double b)
{
    return (a > b)?a:b;
}

int main()
{
    int x = 10;
    int y = 50;
    printf("MAX of x,y = %d\n",max_int(x,y));
    double a = 11.23;
    double b = 8.47;
    printf("MAX of a,b = %f\n",max_double(a,b));
    return 0;
}

In fact, most of the implementations of the standard C libraries have implemented min, max, sort and several other functions using Macro functions (the official term for macro functions is smart macros).

Situation#2: Debugging

Most compilers are smart enough to avoid inline functions if debugging is to be done.  So usually the compiler will ignore the inline keyword and use a regular version of the function if you have debugging turned on and hence it can affect the timing of your code which is especially important if your code is timing sensitive and you want to observe the real-time performance during the debug session.

Also, if you need to add some debug code and you don’t want your added debug code to take up too much time printing out stuff to your debug console, it’s a good idea to use macro functions for improving code performance.

Thus in the above cases at the risk of readability issues, it’s better to use macro functions than inline ones.

That’s the reason most embedded libraries make use of smart macros to implement their logging needs as embedded applications are usually sensitive to timings.

Situation#3: Multiple substitutions

#include<stdio.h>
#define SUM(a,b,c,d) (a) + (b) + (c) + (d)
#define AVERAGE(a,b,c,d) (SUM(a,b,c,d)) /(4)
int main()
{
    int a = 2;
    int b = 3;
    int c = 4;
    int d = 5;
    printf("Sum = %d\n",SUM(a,b,c,d));
    printf("Average = %d\n",AVERAGE(a,b,c,d));
    return 0;
}

Consider the code snippet above. Here the SUM macro is used again in the AVERAGE macro and they are both used separately on the main function. This kind of nested substitutions are only available with macros and not with inline functions.

Where to Use Macro Functions?

Now that we have seen where each one shines, let’s see where each one is more appropriate to use.

Use macros if

  • You are using debug code like printing to a console and you don’t want the debug code to change the timing characteristics of your program too much. This is especially important in timing-sensitive embedded systems codes.
  • You need multiple substitutions
  • You cannot use inline functions

For everything else go for inline or regular functions..!!

Potential Pitfalls of Using Macro Functions and How To Avoid Them

Now that we know where to use macros, it’s worth mentioning some of the side effects that macros can produce and how to avoid them.

Side Effect#1: Parentheses Effect

Consider this simple macro and the code snippet

#include<stdio.h>
#define MULTIPLY(a,b) a*b
int main()
{
    int a = 5;
    int b = 3;
    printf("(a+4) x b = %d\n",MULTIPLY(a+4,b));
    return 0;
}

Here since there are no parentheses in the macro definition, instead of 27 for the result, we will be getting 17.

This is because when text is substituted we will see something like this

printf("(a+4) x b = %d\n", a + 4 * b);

So instead of (a+4) * b,  we have a + 4*b and hence parentheses must be included on all macro definitions like below to get the desired answers.

#define MULTIPLY(a,b) (a)*(b)

Side Effect#2: Multiple Increment Effect

Let’s take a look at the code below.

#include<stdio.h>
#define SQUARE(a) (a)*(a)
int main()
{
    int a = 2;
    printf("Square of a = %d\n", SQUARE(++a));
    printf("a = %d\n",a);
    return 0;
}

Here instead of getting 9, if you execute the code you will get 16.

This is because of the fact that a is getting incremented 2 times due to the text substitutions.

printf("Square of a = %d\n", (++a) * (++a));

As a result, the final value of a will be 4 instead of 3.

These are just 2 of many side effects that can come while using macros, so if you decide to use them, then be super careful!

Some programmers have gone to the level where they consider macros to be evil and never use them in their codes!

This is one of the reasons behind the addition of inline keyword in C and the reason behind the fact that macros are not used in more recent languages like C# and Python.

Before I end this article I would like to leave you with a little bit more information about inline functions and compiler optimizations.

Compiler Optimizations and inline functions

  1. The “inline” keyword is taken as a guideline for the compiler and hence the compilers can choose to ignore it if needed, but we can always force the compiler to inline it using some compiler options.
  2. If a function is static then there is a good chance the compiler will automatically inline it, especially if it is a short one.

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