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

2

46

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!

Monad answered 28/7, 2014 at 17:10 Comment(6)
possible duplicate: stackoverflow.com/questions/7762731/…Reisfield
all are inline functions. static inline is where is a static function, extern inline is a externally declared function, normal inline is simply normal.Dichroism
possible duplicate: https://mcmap.net/q/16705/-what-does-extern-inline-doLimbic
also read https://mcmap.net/q/16701/-what-39-s-the-difference-between-quot-static-quot-and-quot-static-inline-quot-functionDichroism
Possible duplicate of extern inlineBeaux
Is "inline" without "static" or "extern" ever useful in C99? shows the actual syntax necessary in the .h and the extern inline declaration in exactly one .c to instantiate a non-inline definition and make a working C program with a non-static inline function defined in a header and thus able to actually inline, but still link if it doesn't. In C, should inline functions in headers be externed in the .c file? is also about that, with a concise answer.Cusk
E
42

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.

Evin answered 28/7, 2014 at 17:34 Comment(5)
Clarifying your last paragraph: to provide the two separate definitions it is not necessary (and in fact, not advisable) to provide two function bodies. Instead extern inline void f();, which looks like (and is) a declaration, has the effect of transforming an earlier inline void f() { ..... } to be an external definition, instead of an inline definition, for that unit. (Ref. C11 6.7.4/7).Practically
Reading this answer, I'm having trouble understanding the exact meaning of "external definition" vs "internal definition". Where can I find more on that?Malonis
"or providing two separate definitions" inline int il_fun0(void) { return 0; } extern int il_fun0(void) { return 0; } results in a C11 compile error of "error: redefinition of 'il_fun0'" (with or without the extern). `Haihaida
@Malonis Inline definition is used for function inlining. External definition is used for function call as usual. The inline keyword is just hint to compiler, so you can't assure whether your function call would be compiled to the inlining or normal call. As a result, we should prepare two definitions for the one same inline function. Each translation unit calling the inline function must have its own inline definition for it to handle inlining. Furthermore, Only one translation unit must have external definition to handle normal call.Birchfield
@Malonis For example, when you turn off optimization option of your compiler, inline function call would be compiled to normal function call. In this case, all normal calls refer the only one same external definition. Whereas when you turn on optimization option, inline function call would be compiled to inlining. In this case, the inlining is achieved by each translation unit's its own inline definition. So we put inline void func(void) {} in header file for inlining and put extern inline void func(void); in source file(separate translation unit) for normal call.Birchfield
C
2

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 taking a function pointers that doesn't optimize away.

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

Cusk answered 20/1 at 17:45 Comment(7)
Pretty wild, looks about right. With regard to "If all the calls inline, static inline wouldn't have any downside", with -O0 and static inline that snippet seems to compile fine, so maybe it works with static inline regardless and you don't need all the call inline? (i.e. "did you mean 'you can use static inline as a replacement without all the mess'"?)Tellurize
@rogerdpack: Correct, static inline always avoids correctness problems (unless you have functions with static local vars that should be per-program not per-file). The potential downside is code bloat when if it doesn't inline. That's what I was trying to convey. So yes, "if all the calls inline in your optimized builds, you can just use static inline and not worry about this.")Cusk
@rogerdpack: Your last edits distorted some of what I was saying in the paragraph about static inline. I fixed that, and reduced some of the phrasing that makes it sound "crazy". It isn't; it makes perfect sense when you think about it in terms of how linkers work and the fact that exactly one .o needs to define the symbol if there are any references to it. (Because unlike C++, C doesn't require duplicate definitions to be generated and then ignored.)Cusk
OK, yeah that explanation makes more sense, nice. The only question I have is what behavior compilers have for large static inline methods...maybe you could create an explorer example of it not working with a large one? Cheers! :)Tellurize
@rogerdpack: static inline works exactly the same as static. The compiler can inline or not. If there are uses that didn't inline, it emits a definition. But the symbol name is private to that object file so it doesn't conflict at link time with other symbols of the same name in other .o files. In asm, you get the foo: label but not the .global foo directive.Cusk
OK so even if it doesn't inline static inline functions, it's guaranteed to avoid link time failure, is that right? I want to call out the benefits :) Also the firstcall snippet doesn't compile...Thank you :)Tellurize
@rogerdpack: oh, whoops, you're right, even static local vars need constant initializers in C. Only C++ requires compilers to invent guard variables for thread-safe init-once behaviour. And yes, correct, if a static inline doesn't inline, or has a function pointer taken and passed elsewhere, the compiler knows this translation unit is the only one that can define it, because static scope means its private to this translation unit. So it knows it needs to emit a definition for it here, if any references to it are left. And no other translation unit could provide a definition.Cusk

© 2022 - 2024 — McMap. All rights reserved.