inline関数とは?C言語での仕組みと使いどころをわかりやすく解説

C言語で関数を使っていると、「関数呼び出しのコスト」という話を聞くことがあります。

その解決策の一つが「inline関数」です。

この記事では、inline関数の仕組みと、実際に使うべき場面についてわかりやすく解説します。


目次

inline関数とは何か

結論から言うと、inline関数とは

👉 関数呼び出しをせず、処理をその場に展開する仕組みです


■ 通常の関数

C
int add(int a, int b) {
    return a + b;
}

呼び出し時👇

C
int result = add(1, 2);

👉 関数呼び出し(ジャンプや戻り処理)が発生します


■ inline関数

C
inline int add(int a, int b) {
    return a + b;
}

呼び出し時👇

C
int result = add(1, 2);

👉 実際にはこう展開されるイメージ👇

C
int result = 1 + 2;

👉 関数呼び出しが消え、処理がその場に直接埋め込まれます


初心者さん

関数呼んでないんですか?

エンジニアくん

呼んでいるように見えるけど、中身をそのまま貼り付けてるんだよ


C言語文法解説シリーズ

本記事は「C言語文法解説シリーズ」の1つです。
C言語の文法を、組み込み開発の視点も交えて解説しています。

C言語文法解説シリーズ一覧はこちら

なぜinline関数が必要なのか

inline関数が使われる理由は、単に「速くするため」ですが、
重要なのは何がボトルネックになっているのかを理解することです。


■ 関数呼び出しで実際に起きていること

通常の関数呼び出しでは、見た目以上に多くの処理が行われています。

例えば:

C
int result = add(1, 2);

この1行の裏では、

  • 引数をレジスタやスタックにセット
  • 関数へジャンプ(分岐)
  • 戻りアドレスの保存
  • 関数終了後に元の場所へ復帰

といった処理が発生します。

👉 つまり「単なる計算」ではなく、制御の切り替えコストがかかっています。


■ なぜ問題になるのか

例えば関数の中身がこれだけだった場合:

C
int add(int a, int b) {
    return a + b;
}

実際にやっていることは「足し算1回」だけです。

それに対して関数呼び出しでは:

  • ジャンプ
  • スタック操作

👉 本体より周辺処理の方が重くなることがある


■ inline関数ならどうなるか

C
inline int add(int a, int b) {
    return a + b;
}

これを呼び出すと、

C
int result = 1 + 2;

のように展開されます。

👉 関数呼び出しそのものが消えるため、
👉 制御の切り替えコストがなくなる


inline関数のメリット


① 高速化(ただし条件あり)

関数呼び出しがなくなることで高速になりますが、
効果があるのは次のような場合です。

  • 関数が小さい(数行程度)
  • 呼び出し回数が多い(ループ内など)

👉 この2つが揃うと効果が大きくなります


② マクロより安全

似た目的でマクロが使われることがあります。

C
#define ADD(a, b) ((a) + (b))

しかしマクロは単なる「文字列置換」なので、

  • 型チェックがない
  • 副作用に弱い

という問題があります。

例えば:

C
ADD(i++, j++)

👉 意図しない評価順でバグになる可能性があります


■ inline関数なら

  • 型チェックあり
  • 通常の関数として扱える

👉 安全性を保ったまま高速化できる


inline関数の注意点


① 必ずインライン展開されるわけではない

inlineはあくまでコンパイラへのヒントです。

  • 展開した方が良い → インライン化
  • しない方が良い → 通常関数として扱う

👉 最終判断はコンパイラが行います


初心者さん

書けば必ず速くなるんですか?

エンジニアくん

そこはコンパイラ次第。最適な方を選んでくれる


② 大きい関数には向かない

インライン展開されると、コードはその場にコピーされます。

つまり:

  • 呼び出し箇所ごとにコードが増える
  • プログラムサイズが肥大化する

👉 キャッシュ効率が悪くなり、逆に遅くなることもあります


③ ヘッダに書く理由(重要)

inline関数は「展開される」ため、

👉 呼び出し元から関数の中身が見えている必要があります

そのため、

  • ヘッダファイルに定義を書く
  • 各.cから参照できるようにする

という構成になります。

static inline の意味と、なぜこの形が使われるのか

C言語でよく見かける次の書き方:

C
static inline int add(int a, int b) {
    return a + b;
}

この static inline は単なる組み合わせではなく、
**「リンク時の衝突を防ぎつつ、インライン展開を狙うための設計パターン」**です。

ここを理解するには、staticinline をそれぞれ単独で考えるだけでは不十分です。


■ 前提:関数はデフォルトで「外部リンケージ」

まず重要な前提として、C言語の関数は通常

👉 外部リンケージ(external linkage)

を持ちます。

つまり:

  • 別のファイルからも見える
  • リンク時に「同じ名前は1つだけ」でなければならない

■ ヘッダに関数を書くと何が起きるか

例えば、ヘッダファイルに関数を書いたとします:

util.h
int add(int a, int b) {
    return a + b;
}
これを複数の .c ファイルで #include すると、
main.c
#include "util.h"
sub.c
#include "util.h"

👉 add関数が複数回定義されることになります

結果:

👉 リンカエラー(多重定義)


■ inlineだけでは解決しない理由

では inline を付ければいいのか?

C
inline int add(int a, int b) {
    return a + b;
}

👉 実はこれ、環境によっては解決しません

理由:

  • inline は「展開してほしい」というヒント
  • しかし必ず展開されるわけではない
  • 展開されなかった場合、関数として実体が必要になる

👉 結果:やはり多重定義の可能性が残る


■ static を付けると何が変わるか

C
static int add(int a, int b) {
    return a + b;
}

ここで static を付けると、

👉 **内部リンケージ(internal linkage)**になる

つまり:

  • .c ファイルごとに「別の関数」として扱われる
  • 名前が同じでも衝突しない

■ static inline の本質

ここでようやく組み合わせの意味が出てきます:

C
static inline int add(int a, int b) {
    return a + b;
}

これは

  • static → 各翻訳単位ごとに独立した関数にする(衝突回避)
  • inline → 展開して呼び出しコストを減らす

👉 安全性(リンケージ)と性能(インライン化)を両立する書き方


■ なぜヘッダに書くのか

この形が特に使われるのは、ヘッダファイルです。

util.h
static inline int add(int a, int b) {
    return a + b;
}
こうすることで:
  • どの .c からも使える
  • それぞれのファイルで展開される
  • リンク時の衝突が起きない

👉 「ヘッダに書ける関数」の安全な実装方法


■ 組み込みでよく使われる理由

組み込みでは特にこの形が多用されます。

理由は:

  • 小さい関数を多用する(ビット操作・レジスタ操作)
  • ヘッダで共有したい
  • かつオーバーヘッドは減らしたい

👉 その要求にぴったり合うのが static inline


■ 注意点(重要)

① 各ファイルにコピーされる

static のため、各 .c に別々の関数が生成されます。

👉 コードサイズは増える可能性がある


② inlineは保証されない

  • 展開されない場合もある
  • その場合は通常関数として存在する

③ 大きな関数には向かない

  • コード肥大化
  • キャッシュ効率低下

✔ まとめ(理解の芯)

static inline は単なる組み合わせではなく:

  • リンク衝突を防ぐ(static)
  • 高速化を狙う(inline)

👉 ヘッダで使える小さな関数を安全に提供するための設計パターン

static関数についてはこちらの記事で解説しています。


組み込みでのinline関数の使いどころ

組み込み開発では、inline関数は単なる「高速化テクニック」ではなく、設計上の重要な選択肢として使われます。

特に意識すべきなのは、「どの処理に適用すると効果があるか」です。


■ 小さな処理(関数呼び出しコストが相対的に大きい)

例えば、ビット操作のような処理です。

C
static inline void setBit(uint8_t *reg, uint8_t bit) {
    *reg |= (1 << bit);
}

この関数自体は非常に単純で、実行される処理は1〜2命令程度です。

しかし通常の関数として呼び出す場合、

  • 関数呼び出し(ジャンプ)
  • 戻りアドレスの保存
  • 引数の受け渡し(レジスタ or スタック)

といったオーバーヘッドが発生します。

👉 つまり、「本体より呼び出しの方が重い」状態になります。

inline関数にすることで、これらのオーバーヘッドをなくし、

C
*reg |= (1 << bit);

のように直接展開されるため、処理効率が向上します。


■ レジスタ操作(I/Oアクセス)

組み込みでは、ハードウェアレジスタにアクセスする処理が頻繁に発生します。

C
static inline void writeRegister(uint32_t addr, uint32_t value) {
    *(volatile uint32_t *)addr = value;
}

このような関数は、

  • 処理自体は単純
  • しかし呼び出し頻度が非常に高い

という特徴があります。

もし関数呼び出しのオーバーヘッドが毎回発生すると、

👉 リアルタイム性に影響が出る可能性があります

そのため、inlineによって直接展開し、余計な処理を排除することが重要になります。


■ 高頻度で呼ばれる処理(ループ・割り込み)

例えばループ内で呼ばれる関数:

C
for (int i = 0; i < 1000; i++) {
    process();
}

このprocess()が小さい関数であれば、

👉 呼び出し回数 × オーバーヘッド

がそのまま性能低下につながります。

また、割り込み処理内ではさらに重要です。

割り込みは「できるだけ短時間で終わること」が求められるため、

  • 不要な関数呼び出し
  • スタック操作

は極力避ける必要があります。


■ まとめ:なぜ「小さい+頻繁」なのか

ここまでを整理すると、inline関数が有効な条件は次の通りです。

  • 処理自体が軽い(関数本体が短い)
  • 呼び出し回数が多い(オーバーヘッドが積み重なる)

👉 この2つが揃ったときに、inlineの効果が最大になります。

まとめ

  • inline関数は「処理をその場に展開する仕組み」
  • 関数呼び出しのコストを減らせる
  • ただし強制ではなく、コンパイラ次第
  • 小さい関数で効果が大きい
  • 実務では static inline がよく使われる

この記事が参考になった方へ

関数を含むC言語の基本文法をこちらの記事で整理しています。

技術に関するご相談・開発・自動化ツール作成・記事執筆などのご依頼も承っています。

小さなご相談からでもお気軽にご連絡ください。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

組み込みソフトエンジニアとして働きながら、
C言語・メモリ・ポインタなどの基礎から実務まで解説しています。

副業・キャリアについても実体験ベースで発信中です。

X(旧Twitter)でも発信しています
👉 Xはこちら

▼まずはここから読むのがおすすめ
C言語文法シリーズ
メモリ領域解説シリーズ

コメント

コメントする


目次