In C programming, function calls introduce a small overhead due to stack operations, parameter passing, and control transfer. While this overhead is negligible in most applications. But it can become significant in performance-critical systems such as embedded firmware, device drivers, and real-time applications.
To reduce this overhead, the C language provides the inline keyword, which suggests to the compiler that the function body should be expanded directly at the call site instead of performing a normal function call.
In this article, you will learn:
- What an inline function in C is
- How the inline specifier works
- The difference between inline, static inline, and extern inline
- When inline functions should be used
- Practical examples used in embedded systems
What is the inline function in C?
A function declared with the inline specifier is known as an inline function.
The C language introduced the inline keyword in the C99 standard. The inline specifier is a hint given to the compiler to perform an optimization. Its purpose is to suggest that the compiler expand the function body at the point where the function is called, instead of performing a normal function call. However, the compiler is free to ignore this suggestion.
In other words, when you create an inline function, the compiler may replace the function call with the actual body of the function. However, it is important to understand that inline is only a hint to the compiler. The compiler is free to ignore this request depending on optimization settings and other conditions.
Let’s see an example.
inline void Swap(int *a, int *b)
{
int tmp= *a;
*a= *b;
*b = tmp;
}
If the compiler decides to inline this function, every call to Swap() may be replaced with the function body.
Swap(&x, &y);
may internally become:
int tmp = *(&x); *(&x) = *(&y); *(&y) = tmp;
This avoids extra overhead created by the function call such as:
- Stack operations
- Parameter passing
- Jump to function
- Return from function
However, excessive inlining may increase the code size, because the function body is duplicated at every call site.
For example, If Swap() were a large 50-line function and it was called in 100 different places, the compiler might duplicate those 50 lines 100 times when inlining.
This can significantly increase the size of the final compiled binary. In performance-critical or embedded systems, excessive binary size may lead to Instruction Cache (I-Cache) thrashing. If the expanded code no longer fits in the CPU’s ultra-fast L1 instruction cache, the CPU must frequently fetch instructions from slower levels of memory. Ironically, this can make the “optimized” program run slower instead of faster.
Why Use Inline Functions?
An inline function is a function where the compiler may replace the function call with the actual function body. Instead of performing a normal function call, the function’s code is inserted directly at the call site. Inline functions are typically used for small, frequently called functions to reduce the overhead of function calls.
Let’s see a few reasons to use inline functions in your code.
Reduced Function Call Overhead:
In a normal function call, several steps occur behind the scenes:
- Pushing arguments onto the stack: Each argument passed to the function is stored temporarily on the call stack.
- Jumping to the function location: The program counter moves to the function’s code.
- Executing the function and returning: After execution, the program returns to the point where the function was called.
Each of these steps adds extra time to the execution, which can become significant in functions that are called frequently, especially in loops.
Inline functions tell the compiler to replace the function call with the actual code of the function. This eliminates:
- Stack operations for arguments
- Jumping to the function location
- Return instructions
As a result, execution becomes faster because the overhead of the call is removed.
Example,
inline int square(int x)
{
return x * x;
}
int main()
{
int result = square(5); // replaced by: int result = 5 * 5;
}
Here, the compiler replaces square(5) with 5 * 5 directly, saving the overhead of a normal function call.
Faster Execution for Small Functions:
Inline functions are particularly beneficial for short, frequently used functions. Since the compiler replaces the function call with the actual code, it avoids repeated function call overhead, leading to faster execution.
These small functions often include:
- Mathematical helpers: Functions like square(), max(), or min()
- Bit manipulation functions: For setting, clearing, or toggling bits
- Getter/setter functions: Accessor methods in classes that just return or set a value
Example: Getter function
class Point
{
int x;
public:
inline int
getX ()
{
return x;
}
};
Instead of performing a function call every time getX() is used, the compiler inserts the code return x; directly, reducing execution time.
This is why inline functions are most effective for small, frequently called functions. Large functions, when inlined, can actually increase code size and may slow down caching, so they are usually left as normal functions.
Better Readability and Safety Than Macros
Inline functions are often used as a safer alternative to macros. Macros are processed by the preprocessor, which simply performs text substitution without understanding the code structure. This can lead to unexpected results.
Let’s understand it with an example code.
#define SQUARE(x) x*x
If we use it like this:
int result = SQUARE(3+2);
The macro expands to:
3+2*3+2
Because of operator precedence, the result becomes:
3 + 6 + 2 = 11 // Wrong result
The expected result should be 25. But this produces the wrong result because the macro does not automatically add parentheses.
With inline functions, you will not face such macro expansion issues. They also provide better readability, type checking, and safer code execution compared to macros. Let’s understand it with same example.
inline int square (int x)
{
return x * x;
}
Usage:
int result = square(3 + 2);
Now the compiler evaluates it correctly:
(3 + 2) * (3 + 2) = 25
Type Safety
One major advantage of inline functions over macros is type safety.
Macros do not perform type checking because they are handled by the preprocessor before compilation. They simply replace text in the program, which may lead to errors if incorrect data types are passed.
However, inline functions are checked by the compiler, just like normal functions. The compiler verifies that the correct data types are used when calling the function.
Example,
inline int add(int a, int b)
{
return a + b;
}
Usage:
int result = add(5, 10);
Here, the compiler checks that both arguments (a and b) are integers. If you pass the wrong type of value, the compiler will show an error or warning, helping you avoid mistakes.
Argument Evaluation:
Macros can sometimes cause unexpected behavior because they may evaluate arguments multiple times.
Since macros work by text substitution, the same argument can be used more than once in the expanded code. If the argument contains an expression that changes value, it may produce incorrect results.
Example Using Macro
#define SQUARE(x) x * x
Usage:
int a = 3; int result = SQUARE(a++);
After expansion, it becomes:
a++ * a++
Here, a++ is executed twice, which changes the value of a two times and may give an unexpected result.
Inline Function Version
inline int square (int x)
{
return x * x;
}
Usage:
int a = 3;
int result = square(a++);
In this case, the argument a++ is evaluated only once, because inline functions behave like normal functions.
Inline Function vs Macro:
Both inline functions and macros are used to reduce function call overhead by expanding code at the point where it is used.
In early C programming (before the C99 standard introduced the inline keyword), developers relied heavily on preprocessor macros (#define) to avoid the overhead associated with function calls. Macros achieve this by performing simple text substitution before the compilation phase.
However, macros can introduce subtle and hard-to-detect errors because the preprocessor performs substitution without type checking, scope awareness, or proper evaluation of expressions.
To address these limitations, the C99 standard introduced inline functions, which allow the compiler to expand function code at the call site while still preserving the semantics, type safety, and debugging capabilities of normal C functions.
Although both aim to improve performance by reducing function call overhead, they differ significantly in terms of safety, maintainability, debugging support, and behavior.
Let’s see few differences between the macro and inline function.
Advantages of Inline Functions over Macros:
Here are few advantages of inline function over the Macros. You must know before using them.
Type Safety: Inline functions are checked by the compiler for correct types, whereas macros are simple text substitutions and can lead to type-related errors.
Arguments Evaluated Only Once: Inline functions evaluate each argument a single time, preventing unexpected side effects that can occur with macros where arguments may be evaluated multiple times.
Better Debugging Support: Since inline functions are actual functions, debuggers can step through them easily, unlike macros which are expanded as code text.
Follows Scope Rules: Inline functions respect normal scope and visibility rules, while macros do not have scope and can unintentionally affect code outside their intended area.
More Readable and Maintainable: Inline functions have clear syntax and behave like regular functions, making the code easier to read and maintain compared to macros.
Compiler Performs Syntax Checking: Inline functions are compiled like regular functions, so the compiler can catch syntax errors, unlike macros which may silently expand into invalid code.
When Macros Are Still Used:
Although inline functions are often preferred, macros remain useful in specific situations:
Header Guards:
Macros prevent multiple inclusions of the same header file, avoiding redefinition errors:
#ifndef FILE_H
#define FILE_H
// header file content
#endif
Conditional Compilation
Macros allow code to be included or excluded depending on conditions, useful for debugging or platform-specific code:
#ifdef DEBUG
printf("Debug Mode\n");
#endif
Constant Definitions:
Macros can define constants that are replaced at compile time:
#define PI 3.14159
But nowadays, constexpr in C/C++ is preferred over macros for defining constants because it is type-safe, scoped, and evaluated at compile-time.
Important points related to inline function in C:
Any function with internal linkage can be an inline function. See the below example in which I have used the swap function that has internal linkage.
static inline void Swap(int *a, int *b)
{
int tmp= *a;
*a= *b;
*b = tmp;
}
If a non-static function is declared inline, then it must be defined in the same translation unit. The inline definition that does not use extern is not externally visible and does not prevent other translation units from defining the same function. Let’s see an example ( Compiler: -std=C99).
Example 1:
/*main.c*/
#include<stdio.h>
inline void ok();
int main()
{
ok();
return 0;
}
/*test.c*/
inline void ok()
{
//function body
}
Output: Error
Example 2:
/*main.c*/
#include<stdio.h>
void ok();
int main()
{
ok();
return 0;
}
/*test.c*/
#include<stdio.h>
extern inline void ok()
{
printf("%d",10);
}
Output: 10
Now I am going to explain a very important concept. As we know that An inline definition does not provide an external definition for the function, and does not forbid an external definition in another translation unit.
So you can say that an inline definition provides an alternative to an external definition, which a translator may use to implement any call to the function in the same translation unit. It is unspecified whether a call to the function uses the inline definition or the external definition.
Let’s see an example (Compiler: -std=C99).
Example 1:
Creating an inline function name and the definition is an “inline definition”. Also, I am not creating any external definition of the name
#include<stdio.h>
inline const char *name()
{
return "Aticle";
}
int main()
{
printf("%s", name());
return 0;
}
Output:

Note: According to the C standard, If an identifier declared with internal linkage is used in an expression (other than as a part of the operand of a sizeof or _Alignof operator whose result is an integer constant), there shall be exactly one external definition for the identifier in the translation unit.
Example 2:
Now creating an external definition to the function “name”.
/*main.c*/
#include<stdio.h>
inline const char *name()
{
return "Aticle";
}
int main()
{
printf("%s", name());
return 0;
}
/*test.c*/
extern const char *name()
{
return "world";
}
Output: Unspecified (Could call external or internal definition).
You also need to remember that a non-static inline function (function with external linkage) must not contain a definition of a non-const object with static or thread storage duration. Also, it must not contain a reference to an identifier with internal linkage. Consider the below example,
static int data;
inline void fun()
{
static int n = 1; // error: non-const static in a
// non-static inline function
int value = data; // error: non-static inline
// function accesses a static variable
}
Recommended Post
- Function Specifiers in C.
- Type qualifiers in C.
- Punctuators in C.
- Elements of C language.
- C String Literals with Its Types
- C identifiers and naming rules.
- Stringizing operator (#) in C
- Token Pasting Operator in C/C++ programming.
- Learn how to use the typedef in C.
- Macro in C, you should know.
- enum in C,7 application.
- You should know the volatile Qualifier.
- C format specifiers.