1) <span>inline</span> What is it?
- Semantics: It suggests to the compiler that “this function can be expanded inline at the call site, saving the overhead of a function call (saving/restoring registers, jumping/returning, etc.).”
- Not a guarantee: It is merely a suggestion, and the compiler may choose not to adopt it (depending on optimization level, function size, whether the address is taken, recursion, etc.).
- Scope: It can only be inlined where the function body is visible (usually within the same translation unit, or if the function body is written in a header file).
2) Three common usages (that you will definitely use)
A. “Utility functions” in header files
“
Recommended: **
<span>static inline</span>**
// util.h
static inline uint32_t clamp_u32(uint32_t v, uint32_t lo, uint32_t hi) {
return v < lo ? lo : (v > hi ? hi : v);
}
Why add <span>static</span>?
- It gives the function internal linkage (each .c file has its own copy), avoiding “duplicate definition” link errors when multiple .c files include the same header file.
- This is the standard practice for placing inline functions in header files.
B. Public API, while allowing inlining at the call site
“
Safe practice: Declare in the header file, define in the source file (the definition can include
<span>inline</span>or not)
// foo.h
int foo(int x); // Declaration only
// foo.c
inline int foo(int x) { // Definition (inline can be included or not)
return x * 2;
}
- This will definitely generate an external symbol for other .c files to call, and the compiler may inline it within this .c file.
- If you want to inline across files, typically make the “core small function” a
<span>static inline</span>in the header file, and call it from the “external API” in the .c file (see the next section).
C. “Kernel fast path + external API” two-stage (commonly used in embedded systems)
// gpio_fast.h
static inline void gpio_write_fast(volatile uint32_t *reg, uint32_t mask) {
*reg = mask; // Very small function, suitable for inlining
}
// gpio.c
#include "gpio_fast.h"
void gpio_write(volatile uint32_t *reg, uint32_t mask) { // External API
gpio_write_fast(reg, mask);
}
- The small function in the header file can almost always be inlined; the external symbol in the .c file ensures a linkable implementation.
- This balances performance (hot path inlining) and link stability (there is always an actual function).
3) Usage details & quick rules
-
<span>inline</span>can be used with<span>static</span>/<span>extern</span>, recommended order:<span>static inline</span>(the order does not affect semantics). -
Taking the function address (e.g., assigning to a function pointer) or when the compiler decides not to inline, there must be a callable entity:
<span>static inline</span>has a private entity in each .c file, so it won’t be missing.- If a non-
<span>static</span>inline is defined only in a certain .c file and marked as<span>inline</span>, under C99 semantics it may not generate an external entity, leading to link failures when called from other files. Therefore, using the organization methods in B/C above is the most stable. -
Recursive/variadic functions are usually not inlined (or only the leaf layers may be inlined); overly large functions may also not be inlined.
-
Optimization options have a significant impact:
<span>-O2/-O3</span><code><span> are more willing to inline; </span><code><span>-Os</span>tends to reduce size. -
**LTO (Link Time Optimization)** can inline across .o files, but depends on toolchain support and flags (e.g.,
<span>-flto</span>).
4) C99 vs GNU Extensions (pits you must know)
Different compilation modes interpret <span>extern inline</span> differently:
-
C99/C11 standard semantics (
<span>-std=c11</span>/<span>c99</span>): - In .c files:
<span>inline int f(...) {}</span>does not necessarily produce an external entity. - To want an external entity: write another definition without
<span>inline</span>, or simply do not write<span>inline</span>in the .c file. -
GNU89 old semantics (many old projects default to
<span>-std=gnu89</span>): <span>extern inline</span>means “only inline, do not generate an entity”, which behaves differently from C99.-
Recommendation: Standardize to
<span>-std=c11</span>or<span>-std=gnu11</span>, to avoid confusion; for cross-platform libraries, prefer using both A/C modes, which generally won’t lead to pitfalls.
5) When to use <span>inline</span> (rules of thumb)
Suitable for:
- Very short, frequently called functions with no complex branches: bit manipulation, register read/write, short math, boundary clipping, byte order conversion.
- Thin wrappers in hot paths (like GPIO, SPI register read/write wrappers).
Not suitable for:
- Very large functions, complex branches, or functions containing a lot of
<span>printf</span>/locks/blocking calls. - APIs that require stable symbol addresses for interfaces (callback tables) and are frequently used across modules.
6) Trade-offs with macros
-
Common reasons to use
<span>static inline</span>instead of macros:
- Type checking is available;
- Evaluated only once, avoiding macro side effects;
- Facilitates debugging and stepping through code.
Macros still have their place (conditional compilation, generic expressions), but C11’s <span>_Generic</span> + <span>static inline</span> can also create type-safe “generic functions”.
7) Embedded practical examples (ready to use)
Register bit manipulation:
// reg.h
static inline void set_bits(volatile uint32_t *reg, uint32_t mask) {
*reg |= mask;
}
static inline void clr_bits(volatile uint32_t *reg, uint32_t mask) {
*reg &= ~mask;
}
Checksum inline (hot path)
// checksum.h
static inline uint16_t crc16_step(uint16_t crc, uint8_t b) {
crc ^= b;
for (int i = 0; i < 8; ++i)
crc = (crc & 1) ? (crc >> 1) ^ 0xA001u : (crc >> 1);
return crc;
}
Public API + inline fast path (robust practice)
// hb_fast.h
static inline uint16_t hb_crc_acc(uint16_t crc, const uint8_t *buf, size_t n){
for (size_t i = 0; i < n; ++i) crc = crc16_step(crc, buf[i]);
return crc;
}
// hb.c
#include "hb_fast.h"
uint16_t hb_crc(const uint8_t *buf, size_t n) { // External symbol
return hb_crc_acc(0xFFFFu, buf, n);
}
8) Practical considerations (pitfall checklist)
-
Header files =
<span>static inline</span>; .c files’ public functions should be defined normally, do not write<span>inline</span>casually in .c files to avoid missing external entities. -
Function addresses: If you want to place them in callback tables, do not rely on them being inlined; there still needs to be an entity implementation (see scheme C).
-
Debugging: After inlining, stepping through will “jump into the call site”, which is not a bug. If you need to step through processes, you can temporarily disable optimization or add
<span>__attribute__((noinline))</span>. -
Force inline/disable inline (GCC/Clang extensions):
- Force:
<span>__attribute__((always_inline)) inline</span> - Disable:
<span>__attribute__((noinline))</span>evaluate size vs. performance, do not misuse.
Size vs speed: Inlining will increase code size; on MCUs with tight flash memory, inlining in hot paths and retaining calls in cold paths is a better balance.
Cross-file inlining: Without LTO, inlining can only occur within the same .c file; to inline across files, place small functions in header files (<span>static inline</span>).
**With <span>volatile</span>**: Register read/write in inline functions is usually safer (clearer regarding sequences and side effects), and when necessary, use with <span>memory</span> barriers or <span>volatile</span> pointers.
9) Quick reference card (one-sentence version)
- Small functions in header files ⇒ **
<span>static inline</span>**. - Public API ⇒ header files only declare, .c define normally (it is also fine to write
<span>inline</span>in .c when needed). - If you want both speed and stability ⇒ “header file fast path + .c external symbol” two-stage.
- Do not expect inlining to always happen ⇒
<span>inline</span>is a suggestion, not a command. - For compatibility with old projects ⇒ standardize to
<span>-std=c11</span>(or gnu11), and minimize the use of<span>extern inline</span>historical syntax.