EfCom's Software Development Blog

 22/12/2009: C Inline functions in different compliers Back to Efcom Blog

 

 

Hi all. This is the first entry of my blog, in which I’m going to explain every now and then some weird inconsistencies and good/bad practices which might save you time. This first entry is about inline functions.

 

What are inline functions? Traditionally, in C, when we wanted to make a function expand in the spot of the call instead of being called using the regular call foo convention, thus making the call more efficient, we used macros. The problem is that macros, although powerful, are very problematic. Consider the following textbook macro problems:

 

#define MAX(a,b) a > b ? a : b

 

k = MAX(i,j);

 

When expanded, we shall see:

 

k = i > j ? i : j;

 

So far so good. But what will happen in the following scenario?

 

k = MAX(i++,j)+1;

 

Will become:

 

i = i++ > j ? i++ : j + 1;

 

Which is clearly not what we wanted. That’s just the tip of the iceberg, but I don’t want to get into it too deep here. The fundamental problem with macros, is that the expansion is performed by the preprocessor, which is not much more than a text editor, and lacks the understanding of the language.

 

The C++ answer to macros is inline functions. Unlike macros, inline functions expansion is performed by the compiler, which (hopefully) knows the language better than the preprocessor. The compiler can check types, it can understand the meaning of a variable and not propagate expressions such as i++ into the body of the function. What’s in it for me, I’m just a humble C programmer, you ask? Well the thing is inline functions are so great, they are supported in a wide range of C compilers. Good news, you say, and there is, probably, a standard way of doing it? Of course! The problem is that the different implementations follow the standard a bit differently. And on that topic I want to elaborate. We will see examples of how VC and GCC handle the subject.

 

For starters, let’s take a look at the general idea of inlining functions. Consider the following code:

 

Int foo(int a,int b,int c)
{
return a*b*c;
}

i = foo(x,y,z);

If we check the disassembly, we shall see:

        movl    -8(%ebp), %eax     /* Take the third parameter */
movl    %eax, 8(%esp)       /* push it to the stack */
movl    -12(%ebp), %eax

        movl    %eax, 4(%esp)

        movl    -16(%ebp), %eax

        movl    %eax, (%esp)

        call    foo                           /* call the function */
movl    %eax, -4(%ebp)     /* save the return value in i */

 

 

Now we declare the function inline and do not forget to turn on the compiler switch allowing it to inline functions.
Now, the new disassembly looks like:

 

inline int foo(int a,int b,int c)

{

        return a*b*c;

}

 

int main()

{

        volatile int x = 4, y = 3, z = 5;     /* Made volatile so that the compiler                                                                                  wouldn't optimize them away */
volatile int i =        foo(x,y,z);

}

 

$ gcc -O -S ia.c

        movl    -12(%ebp), %eax

        movl    -8(%ebp), %ecx

        movl    -4(%ebp), %edx

        imull   %ecx, %eax

        imull   %edx, %eax

        movl    %eax, -16(%ebp)

 

What we now have is actually performing of the calculation in main instead of the call.

Now, since the compiler needs the body of the function to inline it in place of call, we need to put the body of the function in the header file. And here the problems begin. Apart from inlining the function, each object file that included our header, has a version of the function. In order to link our object files correctly, we need to make sure that these versions are not visible across object file. Let’s see how it is handled in different compilers.

 

GCC – Using gcc, you can declare the functions in several ways:


1.  static inline – in that case, the compiler checks if in that particular object file all of the calls to the function can be inlined, and in that case, never creates a full compiled version of the function. If there is a reference to the function’s assembly code, it does create the function, but since it is declared static, the function is private to the object and no clashes occur during linking. Omitting static in this point will generate multiple symbols error from the linker if the declaration is found in other object files (for example if the function body resides in a header file included several times):


$ gcc -O *.c

/tmp/ccI1dg91.o: In function `foo':

main.c:(.text+0x0): multiple definition of `foo'

/tmp/cckNfnfH.o:funcs.c:(.text+0x0): first defined here

collect2: ld returned 1 exit status

See examples 1a and 1b

 

2.  extern inline – in that case, the function is never compiled on its own, and if there are references to the function that cannot be inlined, it will become and external reference needed to be resolved during linking. Using this combination, allows us to define an inline function inside a header file, which will cause most calls to be inlined, and on top of that, will provide a single compiled copy of the function in a separate object file, to which all the other referenced to the function will refer.

 

See example 2


When compiling without optimization, the compiler will not inline the function and will demand a regular implementation:


$ gcc *.c

/tmp/ccVAqT78.o: In function `main':

main.c:(.text+0x19): undefined reference to `foo'

collect2: ld returned 1 exit status

If we ask to inline, the problems disappear:
$ gcc -O *.c

$

 

VC – Microsoft C compiler is more intuitive, but less predictable in the sense that “it works”. You can declare the functions inline or not, the compiler will make the right choice. Actually, this is how I found these inconsistencies. After debugging the application on VC, I compiled it with GCC upon which all hell broke loose, forcing me to do a bit of inline functions research.

 

To sum up, inline functions have their advantages and disadvantages, but if used with care, can boost the performance of your code. You need to understand how our compiler handles inline functions to use them correctly. I have tried to summarize the most important parts of handling inline functions, but it's advisable to read the compilers documentation to receive the most up to date and accurate info.

 

 

Thanks to Vladimir Oster for this blog

 


Back to Efcom Software Services  |  Back to Top

 
   Design by Anna D