Reputation: 769
What's the difference between a static inline
, extern inline
and a normal inline
C function?
I've seen some vague explanations about this. As far as I've understood, static inline
is not just an inline
function that is meant to only be referred to within a certain file as the static
keyword usually means. The same goes for extern inline
too I guess, it's not the same explanation as with extern
variables. Any answers would be greatly appreciated!
Upvotes: 49
Views: 18654
Reputation: 364338
inline
without static
inline int foo(int a) { return a+1; } // in foo.h, no extern
This gives the compiler a definition it can inline if it wants, for any .c
that includes this header.
If optimization is disabled or the function is huge, it won't inline. Or if you take a function pointer, a non-inline version needs to exist unless constant-propagation can optimize away the indirection.
When it doesn't inline, the resulting .o
/ .obj
will contain a reference to the symbol name for foo()
(e.g. _foo
or foo
) exactly like for normal functions that you didn't declare as inline
. The linker will fill in an address into the machine code when it finds a definition for the symbol.
But (unlike C++) it also won't create a stand-alone callable asm definition of the function in the .o
/ .obj
compiled from a .c
with call-sites where it decided not to inline. There won't be a foo
definition in the symbol table of every .o
, or in fact any of them if you don't tell it to make one.
(C++ does requires redundant code-gen and for the linker to discard duplicate definitions, instead of requiring manual instantiation.)
For your program to link, exactly one .c
(aka translation unit / compilation unit) needs to contain an extern inline
declaration that instantiates a non-inline copy of the function into a .o
. Example .c
file:
#include "foo.h" // Let the compiler see the definition
extern inline int foo(int); // and instantiate in exactly one .c
// or
// extern inline int foo(); // Without repeating the args is also valid
// until C23 makes () equivalent to (void)
It has external linkage, so calls from multiple .c
files can use this definition. Unlike static inline
where each .c
gets its own copy of the asm for any call sites that don't inline, unless the linker is clever about identical code folding.
If you don't do this, you can get a link error even from a single-file program:
// static // static inline can't create link errors
inline int foo(int a) { return a+1; } // would normally be in a separate header
int main(void){
return foo(-1);
}
//extern inline int foo(int); // link error without this
As we can see on the Godbolt compiler explorer with GCC's default of no optimization (-O0
), the compiler doesn't inline any methods (even though they have the inline qualifier), and the asm output doesn't include a foo:
label, but main
includes a call foo
. With either "link to binary" or "execute the code" options, ld
gives an error:
/tmp/ccNB76SP.o: in function `main':
<source>:4: undefined reference to `foo'
Uncommenting the extern inline int foo(int);
lets it link and run, and we can see the asm output from this .c
does now include a foo:
definition of the function.
(The "filter directives" option is on so we don't see the .global foo
which makes this symbol visible in the .o
for other files to link against. See also How to remove "noise" from GCC/clang assembly output?)
With optimization enabled (-O2
or higher), the compiler will (normally) have inlined every call so there won't be any reference in the asm to its symbol name. So your program would link even without the extern inline
declaration / instantiation. (As shown in another pane of the Godbolt link above.) Like with undefined behaviour, practical results of violating the one-definition rule can depend on compiler options like optimization level.
Another option is to use static inline
in the header. If all the call-sites do inline, it wouldn't have any downside like duplicate copies of the same machine code bloating your file size and I-cache footprint. Other than linker details, it's equivalent unless your function uses static
local variables that need to be shared program-wide by one instead of the function, not private to each .c
.
// per-translation-unit (static inline) vs. per-program (inline / extern inline)
// matters for functions like this, unlike for most functions
static inline unsigned sequence_number() {
static unsigned counter = 0; // because each .c has its own copy of this var
return ++counter;
}
Even in optimized builds you could end up with non-inline calls for big functions, or if you take the address of the inline
function and pass the function pointer to another function (which doesn't also get inlined, e.g. because it's big or you didn't use link-time optimization (LTO)). Or anything else that results in a call from a call-site with a runtime-variable function pointer that compile-time constant folding can't resolve to a single possible target.
Normally avoid inline
for big functions, but a function could perhaps become big from itself inlining a couple other functions. The compiler might decide to make a non-inlined (aka outlined) definition that inlines both its children, or might inline this function into callers and maybe or maybe not inline one or both of the callees in this hypothetical example.
Related Q&As
inline
/ extern inline
is kinda messy. It's totally reasonable to only use static inline
in C projects.Upvotes: 5
Reputation: 320531
A function definition with static inline
defines an inline function with internal linkage. Such function works "as expected" from the "usual" properties of these qualifiers: static
gives it internal linkage and inline
makes it inline. So, this function is "local" to a translation unit and inline in it.
A function definition with just inline
defines an inline function with external linkage. However, such definition is referred to as inline definition and it does not work as external definition for that function. That means that even though this function has external linkage, it will be seen as undefined from other translation units, unless you provide a separate external definition for it somewhere.
A function definition with extern inline
defines an inline function with external linkage and at the same time this definition serves as external definition for this function. It is possible to call such function from other translation units.
The last two paragraphs mean that you have a choice of providing a single extern inline
definition for an inline function with external linkage, or providing two separate definitions for it: one inline
and other extern
. In the latter case, when you call the function the compiler is allowed to chose either of the two definitions.
Upvotes: 44