demystify inline functions in c
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 andextern 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”