Forever a noob
Forever a noob

Reputation: 769

What's the difference between static inline, extern inline and a normal inline function?

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

Answers (2)

Peter Cordes
Peter Cordes

Reputation: 364338

Practical details and example for C 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

Upvotes: 5

AnT stands with Russia
AnT stands with Russia

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

Related Questions