web-dev-qa-db-ja.com

Cの `inline`キーワードの使い方は何ですか?

Cのinlineに関するstackoverflowのいくつかの質問を読みましたが、それについてはまだ明確ではありません。

  1. static inline void f(void) {}には、static void f(void) {}との実用的な違いはありません。
  2. Cのinline void f(void) {}は、C++の方法としては機能しません。 Cではどのように機能しますか?
  3. extern inline void f(void);は実際に何をしますか?

Cプログラムでinlineキーワードの使用を実際に見つけたことはありません。他の人のコードでこのキーワードを見ると、ほとんどの場合static inlineでは、staticだけと違いはありません。

34
xiver77

注:この回答で_.c_ファイルと_.h_ファイルについて話すとき、コードを正しくレイアウトしたと仮定します。つまり、_.c_ファイルには_.h_ファイルのみが含まれます。違いは、_.h_ファイルを複数の翻訳単位に含めることができることです。

static inline void f(void) {}には、static void f(void) {}との実用的な違いはありません。

ISO Cでは、これは正しいです。これらの動作は同じです(もちろん、同じTUで異なる宣言をしないと仮定してください!)、唯一の実用的な効果は、コンパイラを異なる方法で最適化することです。

Cのinline void f(void) {}は、C++の方法としては機能しません。 Cではどのように機能しますか? extern inline void f(void);は実際に何をしますか?

これは this answerthis thread で説明されています。

ISO CおよびC++では、ヘッダーファイルでinline void f(void) {}を自由に使用できますが、理由は異なります!

ISO Cでは、外部定義をまったく提供しません。 ISO C++では、外部定義を提供します。ただし、C++には、inline関数の外部定義が複数ある場合、コンパイラーがそれをソートし、そのうちの1つを選択するという追加の規則があります(Cにはありません)。

ISO Cの_.c_ファイル内のextern inline void f(void);は、ヘッダーファイル内のinline void f(void) {}の使用と組み合わせることを意図しています。関数のexternal definitionがその翻訳単位で発行されます。これを行わないと、外部定義がないため、リンクエラーが発生する可能性があります(fの特定の呼び出しが外部定義にリンクするかどうかは指定されていません)。

つまり、ISO Cでは、外部定義の場所を手動で選択できます。または、_static inline_をすべて使用して外部定義を完全に抑制します。しかし、ISO C++では、コンパイラは外部定義がどこに行くかを選択します。

GNU Cでは、状況は異なります(これについては以下で詳しく説明します)。

物事をさらに複雑にするために、GNU C++では、C++コードで_static inline_と_extern inline_を記述できます...それが何をするのか正確に推測したくありません

私はCプログラムでインラインキーワードの使用を実際に見つけたことはなく、他の人のコードでこのキーワードを見ると、ほとんど常に静的なインラインです

多くのコーダーは、自分が何をしているのかを知らず、動作しているように見える何かをまとめるだけです。ここでのもう1つの要因は、見ているコードがISO CではなくGNU Cのために書かれている可能性があることです。

GNU C では、プレーンinlineはISO Cとは異なる動作をします。実際には外部から見える定義を発行するため、プレーンinline関数を含む_.h_ファイルが含まれます。 2つの翻訳単位から、未定義の動作が発生します。

したがって、コーダーがGNU Cでinline最適化ヒントを提供する場合は、_static inline_が必要です。 _static inline_はISO CとGNU Cの両方で機能するので、人々がそのことで落ち着き、エラーを出さずに機能するように見えるのは当然です。

、静的なものと違いはありません。

違いは、コンパイラに速度超過サイズの最適化のヒントを提供することのみを目的としています。最新のコンパイラでは、これは不要です。

20
M.M

Cコードは、コードサイズと実行時間の2つの方法で最適化できます。

インライン関数:

gcc.gnu.org 言う、

関数をインラインで宣言することにより、GCCにその関数の呼び出しを高速化するよう指示できます。 GCCがこれを達成する1つの方法は、その関数のコードを呼び出し元のコードに統合することです。これにより、関数呼び出しのオーバーヘッドがなくなり、実行が高速になります。さらに、実引数の値のいずれかが定数である場合、既知の値によりコンパイル時に単純化が許可されるため、インライン関数のコードをすべて含める必要はありません。コードサイズへの影響はあまり予測できません。オブジェクトのコードは、特定のケースに応じて、関数のインライン化により大きくまたは小さくなります。

そのため、実行時間を改善する目的で使用されるコードに関数を組み込むようコンパイラーに指示します。

inlineを繰り返し実行するフラグやビットトグルの設定/クリアなどの小さな関数を宣言すると、時間に対して大きなパフォーマンスの違いが生じますが、コードサイズが犠牲になります。


非静的インラインおよび静的インライン

再び gcc.gnu.org を参照して、

インライン関数が静的でない場合、コンパイラは他のソースファイルからの呼び出しがあると想定する必要があります。グローバルシンボルはどのプログラムでも1回しか定義できないため、関数を他のソースファイルで定義してはなりません。そのため、その呼び出しは統合できません。したがって、非静的インライン関数は常に通常の方法で自動的にコンパイルされます。


extern inline?

繰り返しになりますが、 gcc.gnu.org はすべてを語っています。

関数定義でinlineとexternの両方を指定した場合、定義はインライン化にのみ使用されます。明示的にそのアドレスを参照した場合でも、関数が単独でコンパイルされることはありません。そのようなアドレスは、あたかも関数を宣言しただけで定義していないかのように、外部参照になります。

インラインとexternのこの組み合わせには、マクロの効果がほとんどあります。これを使用する方法は、これらのキーワードを使用してヘッダーファイルに関数定義を配置し、ライブラリファイルに定義の別のコピー(インラインおよび外部を含まない)を配置することです。ヘッダーファイルの定義により、関数のほとんどの呼び出しがインライン化されます。関数の使用が残っている場合、ライブラリ内の単一のコピーを参照します。


まとめると:

  1. inline void f(void){}の場合、inline定義は現在の翻訳単位でのみ有効です。
  2. static inline void f(void) {}の場合ストレージクラスはstaticであるため、識別子には内部リンケージがあり、inline定義は他の翻訳単位では見えません。
  3. extern inline void f(void);の場合ストレージクラスはexternであるため、識別子には外部リンケージがあり、インライン定義は外部定義も提供します。
22
WedaPashi

6.7.4関数指定子 C11仕様から

6インライン関数指定子で宣言された関数は、インライン関数です。関数をインライン関数にすることは、関数の呼び出しが可能な限り高速であることを示唆しています。138)そのような提案が有効である範囲は、実装定義です。139)

138)たとえば、インライン置換など、通常の関数呼び出しメカニズムの代替手段を使用するインライン置換はテキスト置換ではないであり、新しい関数を作成しません。したがって、たとえば、関数の本体内で使用されるマクロの展開では、関数が呼び出される場所ではなく、関数の本体が表示される時点での定義が使用されます。識別子は、本体が発生するスコープ内の宣言を参照します。同様に、外部定義に加えて発生するインライン定義の数に関係なく、関数には単一のアドレスがあります。

139)たとえば、実装はインライン置換を実行しないを実行したり、インライン宣言のスコープ内の呼び出しに対してのみインライン置換を実行したりします。

この関数が広く使用されていることをコンパイラに提案し、この関数の呼び出しの速度を優先することを要求します。しかし、最新のインテリジェントコンパイラーでは、コンパイラーは関数をインライン化する必要があるかどうかを決定でき、ユーザーからのインライン要求を無視できるため、これは多かれ少なかれ無関係かもしれません。

static inline void f(void) {}には、static void f(void) {}との実用的な違いはありません。

そのため、ほとんどの場合、最新のコンパイラではありません。どのコンパイラでも、no実用的/観察可能な出力の違いがあります。

Cのinline void f(void) {}は、C++の方法としては機能しません。 Cではどのように機能しますか?

どこでもインラインである関数は、C++でどこでもインラインである必要があり、リンカは複数の定義エラーを訴えません(定義は同じでなければなりません)。

実際にextern inline void f(void);行う?

これにより、fへの外部リンクが提供されます。 fは他のコンパイル単位に存在する可能性があるため、コンパイラは別の呼び出しメカニズムを選択して呼び出しを高速化するか、inlineを完全に無視する場合があります。

5
Mohit Jain

すべての宣言(定義を含む)がインラインに言及し、決して外部に言及しない関数。
同じ翻訳単位に定義が必要です。標準では、これをインライン定義と呼びます。
スタンドアロンのオブジェクトコードは発行されないため、この定義を別の翻訳単位から呼び出すことはできません。

この例では、すべての宣言と定義はインラインを使用していますが、externは使用していません。

// a declaration mentioning inline     
inline int max(int a, int b);

// a definition mentioning inline  
inline int max(int a, int b) {  
  return a > b ? a : b;  
}

ここ は、Cのインライン関数およびインラインと外部の使用法をより明確にするためのリファレンスです。

3