C言語とC++言語では、インライン関数はキーワード で修飾された関数でありinline、これには2つの目的があります。
registerストレージクラス指定子に類似しています。[ 1 ]inline、リンケージ動作を変更することです。これは、C/C++ のコンパイルとリンケージが分離されているモデルのために必要です。具体的には、コンパイル時にインライン化を可能にするために、関数の定義 (本体) が使用されるすべての翻訳単位で複製される必要があるためです。関数が外部リンケージを持っている場合、これはリンキング中に衝突を引き起こします(外部シンボルの一意性に違反します)。C と C++ (および GNU C や Visual C++ などの方言) は、これを異なる方法で解決します。[ 1 ]関数inlineはC言語またはC++言語で次のように記述できます。
inline void swap ( int * m , int * n ) { int tmp = * m ; * m = * n ; * n = tmp ; }そして、次のような声明が出された。
swap ( & x , & y );コンパイラがインライン化を行うことを決定した場合(通常は最適化を有効にする必要があります)、以下のように翻訳される可能性があります。
int tmp = x ; x = y ; y = tmp ;スワップを多用するソートアルゴリズムを実装する場合、これにより実行速度を向上させることができます。
C++とC99 は関数をサポートしていますが、その前身であるK&R CとC89はサポートしていません。ただし、意味論は異なります。どちらの場合も、インライン化を強制するわけではありません。コンパイラは、関数をまったくインライン化しないか、一部の場合にのみインライン化するかを自由に選択できます。コンパイラによって、インライン化できる関数の複雑さが異なります。Microsoft Visual C++やGCCのような主流の C++ コンパイラは、関数としてマークされていない関数であっても、適切な関数を自動的にインライン化できるオプションをサポートしています。ただし、コンパイラにすべてのインライン化の決定を任せるためにキーワードを省略することは不可能です。なぜなら、リンカが異なる翻訳単位に重複した定義があると警告するからです。これは、関数をインライン化する必要があることをコンパイラに示唆するだけでなく、コンパイラが関数の呼び出し可能なアウトオブラインコピーを生成するかどうかにも影響するためです (インライン関数のストレージクラスを参照)。inlineinlineinlineinlineinline
GNU C は、提供する方言 gnu89 の一部として、inlineC89 の拡張機能として をサポートしています。ただし、その意味論は C++ および C99 とは異なります。C90 モードの armcc も、inline非標準の拡張機能として を提供しており、その意味論は gnu89 および C99 とは異なります。
一部の実装では、コンパイラに強制的に関数をインライン化させる手段が提供されており、通常は実装固有の宣言指定子によって行われます。
__forceinline__attribute__((always_inline))または__attribute__((__always_inline__))、後者は という名前のユーザー定義マクロとの競合を回避するのに役立ちますalways_inline。これを無分別に使用すると、コードが肥大化(実行ファイルが肥大化)、パフォーマンスの向上はほとんど、あるいは全く得られず、場合によってはパフォーマンスが低下することさえあります。さらに、コンパイラは、インライン化が強制されている場合でも、あらゆる状況で関数をインライン化できるとは限りません。この場合、gccとVisual C++の両方で警告が生成されます。
インライン化を強制することが有効な場合:
inlineコンパイラによって尊重されない(コンパイラのコスト/ベネフィット分析ツールによって無視される)コードの移植性を高めるために、以下のプリプロセッサディレクティブを使用できます。
#ifdef _MSC_VER #define forceinline __forceinline #elif defined(__GNUC__) #define forceinline inline __attribute__((__always_inline__)) #elif defined(__CLANG__) #if __has_attribute(__always_inline__) #define forceinline inline __attribute__((__always_inline__)) #else #define forceinline inline #endif #else #define forceinline inline #endifstatic inlineは、すべてのC言語の方言およびC++において同じ効果を発揮します。必要に応じて、ローカルで可視な(アウトオブラインの)関数を出力します。
ストレージクラスに関係なく、コンパイラはinline修飾子を無視して、すべてのC言語の方言とC++で関数呼び出しを生成できます。
ストレージクラスを関数externに適用した場合と適用しない場合の効果はinline、C言語の方言[ 2 ]とC++ [ 3 ]で異なります。
C99では、定義された関数はinline外部から見える関数を決して生成しませんが、定義された関数はextern inline常に外部から見える関数を生成します。C++とは異なり、翻訳単位間で共有される外部から見える関数を、必要な場合にのみ生成するように要求する方法はありません。
inline宣言が宣言と混在している場合extern inline、または修飾されていない宣言(つまり、修飾子やストレージクラスがない宣言)と混在している場合は、翻訳単位には定義(修飾されていないか、、またはであるかどうinlineかにかかわらず)が含まれている必要があり、それに対して外部から見える関数が出力されます。inlineextern inline
定義された関数は、inlineプログラム内のどこかに、定義済みextern inlineまたは修飾子なしの同じ名前の関数が 1 つだけ必要です。プログラム全体でそのような定義が 1 つ以上ある場合、リンカは重複シンボルについて警告します。ただし、定義がない場合でも、すべての使用箇所をインライン化できるのであれば不要となるため、リンカは必ずしも警告しません。しかし、コンパイラは常にinline修飾子を無視して代わりにその関数への呼び出しを生成できるため、リンカは警告する可能性があります。これは、最適化なしでコードをコンパイルする場合によく起こります。(関数が常にどこでもインライン化されるべきであり、そうでない場合はエラーが発生するべきである場合は、これが望ましい動作かもしれません。)便利な方法は、inlineヘッダー ファイルで関数を定義し、関数ごとに 1 つの .c ファイルを作成し、extern inlineその宣言と定義を含む対応するヘッダー ファイルをインクルードすることです。宣言がインクルードの前か後かは関係ありません。
関数のすべての使用箇所がインライン化された場合に、到達不能なコードが最終実行ファイルに追加されるのを防ぐため、単一の関数を含むすべての .c ファイルのオブジェクト ファイルを静的ライブラリファイル (通常は)に配置し、個々のオブジェクト ファイルではなくそのライブラリに対してリンクすることが推奨されます[ 3 ]。これにより、オブジェクト ファイルを直接リンクする場合とは異なり、実際に必要なオブジェクト ファイルのみがリンクされます。オブジェクト ファイルを直接リンクすると、常に実行ファイルに含まれることになります。ただし、ライブラリ ファイルはリンカ コマンドラインで他のすべてのオブジェクト ファイルより後に指定する必要があります。ライブラリ ファイルの後に指定されたオブジェクト ファイルから関数への呼び出しはリンカによって考慮されないためです。関数から他の関数への呼び出しはリンカによって自動的に解決されます (のオプションでこれが保証されます)。extern inlinear rcsinlineinlinesar rcs
別の解決策として、ライブラリの代わりにリンク時-Wl,--gc-sections最適化を使用する方法があります。gcc には、すべての関数が使用されていないセクションを省略するフラグが用意されています。これは、単一の未使用関数のコードを含むオブジェクト ファイルの場合に該当しますextern inline。ただし、未使用関数に関連するセクションだけでなく、他のすべてのオブジェクト ファイルから他のすべての未使用セクションも削除します。(プログラム自体ではなく、プログラマがデバッガextern inlineから呼び出す関数を実行可能ファイルにリンクしたい場合があります。たとえば、プログラムの内部状態を調べる場合などです。)この方法では、関数ごとに .c ファイルを作成する代わりに、すべての関数を含む単一の .c ファイルを使用することもできます。その場合、ファイルは でコンパイルする必要があります。ただし、gcc のマニュアル ページには、「これらのオプションは、使用することで大きなメリットがある場合にのみ使用してください」と警告されています。extern inline-fdata-sections -ffunction-sections
まったく異なるアプローチとして、ヘッダー ファイルで関数をstatic inlineの代わりにとして定義することを推奨する人もいます。 [ 2 ]そうすれば、到達不能なコードは生成されません。ただし、このアプローチには逆の場合に欠点があります。関数が複数の翻訳単位でインライン化できない場合、重複したコードが生成されます。生成された関数コードは異なるアドレスを持つ必要があるため、翻訳単位間で共有できません。これがもう 1 つの欠点です。ヘッダー ファイルで として定義された関数のアドレスを取得すると、異なる翻訳単位で異なる値が得られます。したがって、関数は 1 つの翻訳単位でのみ使用される場合にのみ使用する必要があります。つまり、関数はヘッダー ファイルではなく、それぞれの .c ファイルにのみ記述する必要があります。inlinestatic inlinestatic inline
inlineおよびの gnu89 のセマンティクスは、extern inline基本的に C99 のそれと正反対です[ 4 ]。ただし、gnu89 では関数を修飾extern inlineされていない関数として再定義できますが、C99ではinlineできません[ 5 ] 。したがって、extern inline再定義なしの gnu89 は C99 に似ておりinline、gnu89inlineは C99 に似ていますextern inline。言い換えると、gnu89 では、定義された関数はinline常に であり、定義された関数はextern inline外部から見える関数を出力しません。この根拠は、変数に一致するためです。変数は、として定義されている場合はストレージが予約されずextern、なしで定義されている場合は常に予約されます。対照的に、C99 の根拠は、を使用することで、その名前が示唆する内容に反して、常にインライン化されていないバージョンの関数を出力するという副作用が発生すると、驚くべきことになるということです。inline
C99における、インライン関数に対して外部から見える関数インスタンスを1つだけ提供する必要があること、およびそれによって生じる到達不能コードの問題に関する指摘は、必要な変更を加えてGNU89にも同様に当てはまります。
バージョン 4.2 までの gcc は、明示的に指定されていinlineても gnu89 セマンティクスを使用していました-std=c99。[ 6 ]バージョン 5 では、[ 5 ] gcc は gnu89 から gnu11 方言に切り替わり、inlineデフォルトで C99 セマンティクスが有効になりました。代わりに gnu89 セマンティクスを使用するには、またはを使用して明示的に有効にする必要があります。-std=gnu89インライン化のみに影響する場合は、、またはすべての宣言に属性-fgnu89-inlineを追加します。C99 セマンティクスを保証するには、、、または(なし) を使用できます。[ 3 ]gnu_inlineinline-std=c99-std=c11-std=gnu99-std=gnu11-fgnu89-inline
C++ では、定義された関数はinline、必要に応じて、翻訳単位間で共有される関数を生成します。これは通常、その関数が必要なオブジェクト ファイルの共通セクションに配置することによって行われます。関数は、常にinline修飾子を付けて、どこでも同じ定義を持つ必要があります。C++ では、extern inlineは と同じですinline。C++ のアプローチの根拠は、到達不能なコードを排除するための特別な対策を講じる必要がなく、通常の関数と同様に、 が指定されているかどうかに関係なく違いがないため、プログラマにとって最も便利な方法であるという点ですextern。
修飾子inlineは、クラス定義の一部として定義された関数に自動的に追加されます。
C90 モードの armcc は、C++ と同じセマンティクスをextern inline提供します。このような定義は、必要に応じて翻訳単位間で共有される関数を出力します。C99 モードでは、常に関数を出力しますが、C++ と同様に、翻訳単位間で共有されます。したがって、同じ関数を異なる翻訳単位で定義できます。[ 7 ]これは、初期化されていないグローバル変数の複数の非定義に対するUnix C コンパイラの従来の動作と一致します[ 8 ]。inlineextern inlineextern inlineextern
関数のアドレスを取得するには、inlineその関数のインライン化されていないコピーを生成するコードが必ず必要となる。
C99 では、関数inlineまたは関数はグローバル変数にextern inlineアクセスしたり、非ローカル変数を定義したりしてはなりません。ローカル変数は、関数がインライン化されたか、または呼び出しが行われたかによって、異なる翻訳単位で異なるオブジェクトになる場合とならない場合があります。定義のみが、制限なく内部リンケージを持つ識別子を参照できます。これらは、各翻訳単位で異なるオブジェクトになります。C++ では、ローカル変数と非ローカル変数の両方が許可されており、すべての翻訳単位で同じオブジェクトを参照します。staticconststaticconst staticstatic inlineconstconststatic
alloca、goto、goto、setjmp、__builtin_longjmp、__builtin_return、または__builtin_apply_args。MSDN の Microsoft 仕様に基づくと、MS Visual C++ は、( を使用した場合でも__forceinline)、
#pragma inline_recursion(on)。プラグマを使用すると、再帰関数はデフォルトで 16 回の呼び出しの深さまでインライン化されます。インライン化の深さを減らすには、inline_depthプラグマを使用します。__declspecマークも付いています。インライン展開全般に関する問題点(「インライン展開 § パフォーマンスへの影響」を参照)に加えて、inline関数は言語機能として、見た目ほど価値がない場合もあります。理由はいくつかあります。
inline関数の)コードがそのクライアント(呼び出し元の関数)に公開されるということです。inlineは、関数がどこかで使用される場合、関数の外部定義が 1 つだけ必要です。プログラマがそのような定義を提供しなかった場合、リンカー エラーが簡単に発生する可能性があります。これは、通常インライン化を防止する最適化が無効になっている場合に発生する可能性があります。一方、定義を追加すると、プログラマがリンク用のライブラリに配置したり、リンク時の最適化を使用したり、または を使用したりして注意深く回避しない限り、到達不能なコードが発生する可能性がありますstatic inline。inlineが、通常の関数は単一のモジュールでのみ定義すれば十分です。そうしないと、単一のモジュールを他のすべてのモジュールから独立してコンパイルすることができなくなります。コンパイラによっては、インライン化できなかった使用箇所があるモジュールごとに、それぞれのオブジェクトファイルに関数のコードのコピーが含まれる場合があります。インライン 指定子を持つ関数宣言は、インライン関数を宣言します。インライン指定子は、呼び出し箇所での関数本体のインライン置換を、通常の関数呼び出しメカニズムよりも優先することを実装に指示します。実装は、呼び出し箇所でこのインライン置換を実行する必要はありません。ただし、このインライン置換が省略された場合でも、7.1.2で定義されているインライン関数に関するその他の規則は遵守されなければなりません。
— ISO/IEC 14882:2011、現行のC++規格、セクション7.1.2
インライン関数指定子で宣言された関数はインライン関数です 。関数をインライン関数にすると、その関数への呼び出しが可能な限り高速になることを意味します。このような提案がどの程度効果的かは実装依存です(注:たとえば、実装によってはインライン置換を全く行わない場合や、インライン宣言のスコープ内の呼び出しに対してのみインライン置換を行う場合があります) 。
インライン定義は、 関数の外部定義を提供するものではなく、また、別の翻訳単位における外部定義を禁止するものでもありません。インライン定義は、外部定義の代替手段を提供し、翻訳者はこれを用いて、同じ翻訳単位内で関数への呼び出しを実装することができます。関数呼び出しにおいて、インライン定義が使用されるか外部定義が使用されるかは規定されていません。
— ISO 9899:1999(E)、C99規格、セクション6.7.4
{{cite journal}}:ジャーナルを引用するには|journal=(ヘルプ)-fno-common