Nearby lessons

84 of 124

C - Inline Functions

Inline functions ask the compiler to paste a function's body at the call site instead of making a real call. Learn the inline keyword, how it differs from a macro, and why static inline is the form you should almost always use.

The inline Keyword

Add inline before the return type. The compiler may then replace each call with a copy of the body, skipping the call overhead:

Example01
CCode Cell
1#include <stdio.h>
2 
3static inline int square(int n)
4{
5 return n * n;
6}
7 
8static inline int maxOf(int a, int b)
9{
10 return a > b ? a : b;
11}
12 
13int main()
14{
15 printf("square(7) = %d\n", square(7));
16 printf("maxOf(12, 8) = %d\n", maxOf(12, 8));
17 return 0;
18}
Output
square(7)     = 49
maxOf(12, 8)  = 12

What "Inlining" Actually Means

In simple words: a normal call pushes a stack frame, jumps, runs, and jumps back. Inlining removes all of that by copying the body straight into the caller — as if you had written the expression by hand. You get the speed of hand-written code with the readability of a function.
Example02
CCode Cell
1/* What you write */
2static inline int square(int n) { return n * n; }
3 
4int main()
5{
6 int result = square(5) + square(3);
7 return 0;
8}
9 
10/* What the compiler may generate - no calls at all */
11int main()
12{
13 int result = (5 * 5) + (3 * 3);
14 return 0;
15}
Output
Same result, no function-call overhead

Why static inline

Plain inline in C has awkward linkage rules: if the compiler chooses not to inline a call, it needs an external definition to call — and there may not be one. static inline gives every translation unit its own copy and sidesteps the problem entirely:

Example03
CCode Cell
1/* ---------- utils.h ---------- */
2#ifndef UTILS_H
3#define UTILS_H
4 
5/* static inline in a header: safe, no linker errors */
6static inline int square(int n) { return n * n; }
7static inline int isEven(int n) { return n % 2 == 0; }
8 
9#endif
10 
11/* ---------- main.c ---------- */
12#include <stdio.h>
13#include "utils.h"
14 
15int main()
16{
17 printf("square(6) = %d\n", square(6));
18 printf("isEven(6) = %s\n", isEven(6) ? "yes" : "no");
19 return 0;
20}
Output
square(6)  = 36
isEven(6)  = yes

inline Is Only a Hint

The compiler is free to ignore inline, and free to inline functions you never marked. Optimisation level matters more than the keyword:

Example04
CCode Cell
1#include <stdio.h>
2 
3/* Marked inline, but too big - the compiler will likely refuse */
4static inline int complexCalculation(int n)
5{
6 int i, result = 0;
7 for (i = 0; i < n; i++)
8 for (int j = 0; j < n; j++)
9 result += i * j;
10 return result;
11}
12 
13/* Not marked, but tiny - gcc -O2 will inline it anyway */
14static int tiny(int n) { return n + 1; }
15 
16int main()
17{
18 printf("complexCalculation(4) = %d\n", complexCalculation(4));
19 printf("tiny(41) = %d\n", tiny(41));
20 return 0;
21}
Output
complexCalculation(4) = 36
tiny(41)              = 42

Inline Functions vs Macros

Both avoid call overhead, but only one is type-checked and evaluates its arguments once:

static inline function#define macro
Handled byThe compilerThe preprocessor
Type checkingYesNo
Arguments evaluatedOnceEvery time they appear
DebuggableYesBarely
Needs defensive parenthesesNoYes
VerdictPreferredOnly for compile-time tricks
Example05
CCode Cell
1#include <stdio.h>
2 
3#define SQUARE_MACRO(x) ((x) * (x))
4static inline int squareInline(int x) { return x * x; }
5 
6int calls = 0;
7int next(void) { calls++; return calls; } /* returns 1, 2, 3, ... */
8 
9int main()
10{
11 int result;
12 
13 calls = 0;
14 result = SQUARE_MACRO(next()); /* becomes next() * next() */
15 printf("macro : result %d, next() ran %d times\n", result, calls);
16 
17 calls = 0;
18 result = squareInline(next()); /* next() runs once */
19 printf("inline : result %d, next() ran %d times\n", result, calls);
20 return 0;
21}
Output
macro  : result 2, next() ran 2 times
inline : result 1, next() ran 1 times

The Double-Evaluation Trap

The macro expands to ((next()) * (next())) — the argument text is substituted twice, so the side effect happens twice and the answer is wrong. The inline function receives one already-evaluated argument, so it cannot misbehave this way.

This is the single strongest argument for inline functions over macros. A macro is textual substitution: any argument with a side effect — i++, getchar(), f() — is duplicated. An inline function evaluates each argument exactly once, like every other function call.
Example06
CCode Cell
1#include <stdio.h>
2 
3#define MAX_MACRO(a, b) ((a) > (b) ? (a) : (b))
4static inline int maxInline(int a, int b) { return a > b ? a : b; }
5 
6int calls = 0;
7int next(void) { calls++; return calls; } /* returns 1, 2, 3, ... */
8 
9int main()
10{
11 int result;
12 
13 calls = 0;
14 result = MAX_MACRO(next(), 0); /* next() runs in the test AND again */
15 printf("MAX_MACRO : result %d, next() ran %d times\n", result, calls);
16 
17 calls = 0;
18 result = maxInline(next(), 0);
19 printf("maxInline : result %d, next() ran %d times\n", result, calls);
20 return 0;
21}
Output
MAX_MACRO : result 2, next() ran 2 times
maxInline : result 1, next() ran 1 times

Good Candidates for Inlining

Small accessors, converters and predicates called in tight loops:

Example07
CCode Cell
1#include <stdio.h>
2 
3/* All ideal: one expression, called constantly */
4static inline int isPositive(int n) { return n > 0; }
5static inline int absValue(int n) { return n < 0 ? -n : n; }
6static inline float toFahrenheit(float c) { return c * 9 / 5 + 32; }
7static inline int clamp(int v, int lo, int hi)
8{
9 return v < lo ? lo : (v > hi ? hi : v);
10}
11 
12int main()
13{
14 printf("isPositive(-3) = %d\n", isPositive(-3));
15 printf("absValue(-15) = %d\n", absValue(-15));
16 printf("toFahrenheit(37) = %.1f\n", toFahrenheit(37.0f));
17 printf("clamp(150, 0, 100) = %d\n", clamp(150, 0, 100));
18 return 0;
19}
Output
isPositive(-3)      = 0
absValue(-15)       = 15
toFahrenheit(37)    = 98.6
clamp(150, 0, 100)  = 100

When Not to Inline

Inlining copies code. Do it to a large function called from fifty places and your binary balloons — which can make the program slower by pushing code out of the CPU's instruction cache.

Inline itLeave it alone
One or two linesDozens of lines
Called in a hot loopCalled once at startup
Simple arithmetic or a comparisonContains loops or heavy branching
Accessors and predicatesRecursive functions
Header-only helpersAnything that does I/O

Common Mistakes

  • Plain inline in a header — risks undefined reference at link time. Use static inline.
  • Assuming the keyword forces inlining — it is a hint. Use __attribute__((always_inline)) on gcc if you truly must.
  • Inlining large functions — bigger binary, worse cache behaviour.
  • Trying to inline a recursive function — the compiler cannot expand it fully.
  • Optimising before measuring — profile first; inlining rarely turns out to be the bottleneck.
Trainer's Note: inline does nothing at -O0, which is where most beginners compile. Modern compilers at -O2 inline aggressively on their own, guided by measurement rather than keywords. Write clear small functions and let the optimiser work.
📝 Key Takeaways
  • inline is a hint, not a command — the compiler decides.
  • Use static inline in headers to avoid linker errors.
  • Inline functions are type-checked; macros are not.
  • Inlining suits small, frequently called functions.
  • Inlining large functions makes the program bigger and often slower.

🧠 Test Your Knowledge

4 Questions
Progress: 0 / 4