inline functions in c have always been mysterious: sometimes we get undefined references, sometimes we get multiple definitions; in fact, inline is just a hint for compilers to inline a function with the given inline definition; this is an opportunity not an obligation: the compiler can choose not to inline this function even if it sees inline;

to further understand its details, we write some small tests to see how inline actually works in c; all these tests are compiled with gcc -std=c99, which is the c standard that introduced inline functions (despite an earlier existence of inline in some compiler dialects);

test layout

the test involves 5 files in the same dir, whose layout is as follows:

.
├── bar.c
├── foo.c
├── lib.c
├── lib.h
└── main.c

as expected, the inline function func is defined in lib.h, and used by both foo.c and bar.c; main.c simply provides the main function required to run the compiled and linked program; lib.c is initially empty; below are their contents:

//  bar.c;
#include "lib.h"
int bar()
{
    return func();
}

//  foo.c;
#include "lib.h"
int foo()
{
    return func();
}

//  lib.c;
;

//  lib.h;
static inline int func()
{
    return 1;
}

//  main.c;
int foo();
int bar();
int main()
{
    return foo() + bar();
}

in the tests below, we will modify lib.h (and other files if necessary); if everything is good, the program should return 2;

test 1

our first test uses static inline; no file content is changed; and the program indeed returns 2;

here the function func is included in both foo.c and bar.c; both compilation units contain the same definition of func; in fact, the compiler chooses not to inline this function, but there are no collisions because func is static; to make the compiler inline this function, add -O2, but the program still returns 2 as expected; so this approach works whether the function is inlined or not; this is indeed one (and often the simplest) way to make things work;

however, there is a caveat with this approach: func is compiled as two separate functions in the two compilation units; and if we compare their function pointers, they are not the same;

test 2

this test uses extern inline for func:

//  lib.h;
extern inline int func()
{
    return 1;
}

gcc complains about multiple definitions of func;

here func is still compiled into both compilation units, but extern makes func visible for linking in both compilation units; thus the linker sees two definitions of func and complains;

please do not forget we are running gcc with -std=c99 option; this example can give different results when using other standards (which is quite some fun if you try -std=gnu89, then add -O2, etc.);

test 3

this test uses inline (without static or extern) for func:

//  lib.h;
inline int func()
{
    return 1;
}

gcc complains about undefined reference to func;

as you can guess, it complains because it cannot find a (normal) definition of func; there is a quick solution that may be surprising: compile with -O2; this makes the compiler take the inline definition of func; this solution works for this example, but is not a silver bullet in general: we can fail it if we take the address of func:

//  bar.c;
#include "lib.h"
int bar()
{
    return func() + *((int *)func);
}

gcc will again complain undefined reference to func;

note that there is a difference from test 1: in test 1, static inline makes a normal definition of func when the compiler chooses not to inline, but here, inline does not do the same thing;

so what is the correct solution in general? here func is referred to as inline definition by the c standard; the compiler does not emit code for this inline definition; so there must be a definition somewhere else; a good place to provide this definition is lib.c; so we change its content into this:

//  lib.c;
#include "lib.h"
extern inline int func();

here the magic is extern inline, which turns an earlier inline definition into external definition; this happens only in lib.c, which is the sole file that provides the definition of func at link time; there must be exactly one such file; if you also do the same thing in foo.c or bar.c, then gcc will complain about multiple definitions; (however, you can omit lib.c and do it in one of foo.c and bar.c;)

the fun with -std=gnu89

we mentioned gnu89 in the text above; so what is the fun with it?

the fun is, with -std=gnu89 the solution we give in test 3 does not work any more; to make it work, we need to remove extern in lib.c and add it in lib.h:

//  lib.c
#include "lib.h"
inline int func();
//  lib.h
extern inline int func()
{
    return 1;
}

this is because inline and extern inline kinda work in the opposite way in gnu89 (in contrast with c99); so, as you can guess, using inline for func in lib.h does not work, either:

//  lib.h;
inline int func()
{
    return 1;
}

gcc will complain about multiple definitions, for the same reason in test 2;

you may try other standards if you wish, but i will stop for now;

conclusion

to make inline work properly, first we need to agree upon the c standard; i recommend using c99 (or later); using gnu89 is discouraged because of its inverted behavior; second we need to agree upon the strategy for using inline:

  • if the usage is simple, static inline (as test 1) is the most convenient;

  • otherwise, use inline in .h file and extern inline in exactly one .c file; the .c should include .h as a header file, so that only one definition needs to be written;

both strategies only require writing inline definition once, which avoids possible inconsistencies;

in the end, here is a quote from linus in 2001:

  • “static inline” means “we have to have this function, if you use it but don’t inline it, then make a static version of it in this compilation unit”

  • “extern inline” means “I actually have an extern for this function, but if you want to inline it, here’s the inline-version”

to supplement his quote (below is mine):

  • “inline” means “I have an inline definition for this compilation unit, but if you do not want to inline it, find a normal definition elsewhere”