I understand that inline by itself is a suggestion to the compiler, and at its discretion it may or may not inline the function, and it will also produce linkab
Instead of answering "what does it do?", I'm answering "how do I make it do what I want?" There are 5 kinds of inlining, all available in GNU C89, standard C99, and C++:
Add __attribute__((always_inline)) to any declaration, then use one of the
below cases to handle the possibility of its address being taken.
You should probably never use this, unless you need its semantics (e.g. to affect the assembly in a certain way, or to use alloca). The compiler usually knows better than you whether it's worth it.
__attribute__((weak))
void foo(void);
inline void foo(void) { ... }
Note that this leaves a bunch of copies of the same code lying around, and the linker picks one arbitrarily.
__attribute__((gnu_inline))
extern inline void foo(void) { ... }
The hinted version emits a weak symbol in C++, but a strong symbol in either dialect of C:
void foo(void);
inline void foo(void) { ... }
Or you can do it without the hint, which emits a strong symbol in both languages:
void foo(void) { ... }
Generally, you know what language your TU is when you're providing the definitions, and probably don't need much inlining.
static inline void foo(void) { ... }
For all of these except the static one, you can add a void foo(void) declaration above. This helps with the "best practice" of writing clean headers, then #includeing a separate file with the inline definitions. Then, if using C-style inlines, #define some macro differently in one dedicated TU to provide the out-of-line definitions.
Don't forget extern "C" if the header might be used from both C and C++!