web-dev-qa-db-ja.com

「静的」または「外部」のない「インライン」はC99で有用ですか?

このコードをビルドしようとすると

inline void f() {}

int main()
{
    f();
}

コマンドラインを使用する

gcc -std=c99 -o a a.c

リンカエラー(fへの未定義の参照)が表示されます。 inlineの代わりにstatic inlineまたはextern inlineを使用する場合、または-Oでコンパイルする場合(関数が実際にインライン化される場合)、エラーは消えます。

この動作は、C99標準の6.7.4項(6)で定義されているようです。

変換単位内の関数のすべてのファイルスコープ宣言にinlineなしのextern関数指定子が含まれている場合、その変換単位の定義はインライン定義です。インライン定義は、関数の外部定義を提供せず、別の翻訳単位の外部定義を禁止しません。インライン定義は、外部定義に代わるものを提供します。翻訳者は、この定義を使用して、同じ変換単位で関数の呼び出しを実装できます。関数の呼び出しでインライン定義を使用するか外部定義を使用するかは指定されていません。

これをすべて正しく理解すると、上記の例のようにinlineと定義された関数を持つコンパイルユニットは、同じ名前の外部関数もある場合にのみ一貫してコンパイルされ、自分の関数か外部関数が呼び出されます。

この動作は完全に巧妙ではありませんか? C99でinlineまたはstaticなしで関数externを定義するのは便利ですか?何か不足していますか?

回答の要約

もちろん、私は何かを逃していました、そしてその振る舞いは無意味ではありません。 :)

Nemoの説明 のように、アイデアは関数のdefinitionを置くことです

inline void f() {}

ヘッダーファイルと宣言のみ

extern inline void f();

対応する.cファイル内。 extern宣言のみが、外部から見えるバイナリコードの生成をトリガーします。実際、.cファイルではinlineの使用はありません。ヘッダーでのみ有用です。

Jonathanの回答で引用されたC99委員会の根拠 が説明しているように、inlineはすべて、呼び出しのサイトで関数の定義を表示する必要があるコンパイラ最適化に関するものです。これは、ヘッダーに定義を配置することによってのみ実現できます。もちろん、ヘッダーの定義は、コンパイラーから見られるたびにコードを発行してはなりません。ただし、コンパイラは実際に関数をインライン化することを強制されていないため、外部定義はどこかに存在する必要があります。

90
Sven Marnach

実際、この優れた答えはあなたの質問にも答えています。

外部インライン

アイデアは、ヘッダーファイルで「インライン」を使用し、次に.cファイルで「外部インライン」を使用できるというものです。 「外部インライン」とは、どのオブジェクトファイルに(外部から見える)生成されたコードを含めるべきかをコンパイラに指示する方法です。

[更新、詳しく説明する]

.cファイルでは、「インライン」(「静的」または「外部」なし)の使用はないと思います。ただし、ヘッダーファイルでは意味があり、スタンドアロンコードを実際に生成するには、.cファイルで対応する「外部インライン」宣言が必要です。

37
Nemo

標準(ISO/IEC 9899:1999)自体から:

付録J.2未定義の動作

  • ...
  • 外部リンケージを持つ関数はinline関数指定子で宣言されていますが、同じ変換単位(6.7.4)でも定義されていません。
  • ...

C99委員会は 理論的根拠 を作成し、次のように述べています。

6.7.4関数指定子

C99の新機能:C++から採用されたinlineキーワードは、function-specifierであり、関数でのみ使用できます宣言。関数の定義を呼び出しサイトで表示する必要があるプログラムの最適化に役立ちます。 (標準はこれらの最適化の性質を指定しようとしないことに注意してください。)

関数に内部リンケージがある場合、または関数に外部リンケージがあり、呼び出しが外部定義と同じ変換単位にある場合、可視性が保証されます。これらの場合、関数の宣言または定義におけるinlineキーワードの存在は、その関数の呼び出しがinlineキーワードなしで宣言された他の関数の呼び出しに優先して最適化されるという設定を示すこと以上の効果はありません。

可視性は、呼び出しが関数の定義とは異なる変換単位にある外部リンケージを持つ関数の呼び出しの問題です。この場合、inlineキーワードを使用すると、呼び出しを含む変換単位に、関数のローカル定義またはインライン定義も含めることができます。

プログラムには、外部定義付きの翻訳単位、インライン定義付きの翻訳単位、および宣言はあるが関数の定義はない翻訳単位を含めることができます。後者の翻訳単位での呼び出しは、通常どおり外部定義を使用します。

関数のインライン定義は、外部定義とは異なる定義と見なされます。インライン定義が見える場所で外部リンケージを使用して関数funcの呼び出しが発生した場合、動作は内部リンケージで別の関数、たとえば__funcを呼び出した場合と同じです。適合プログラムは、どの関数が呼び出されるかに依存してはなりません。これは、標準のインラインモデルです。

適合プログラムは、インライン定義を使用した実装に依存してはならず、外部定義を使用した実装に依存してはなりません。関数のアドレスは常に外部定義に対応するアドレスですが、このアドレスを使用して関数を呼び出すと、インライン定義が使用される場合があります。したがって、次の例は期待どおりに動作しない可能性があります。

inline const char *saddr(void)
{
    static const char name[] = "saddr";
    return name;
}
int compare_name(void)
{
    return saddr() == saddr(); // unspecified behavior
}

実装ではsaddrの呼び出しの1つにインライン定義を使用し、もう1つに外部定義を使用する可能性があるため、等値演算は1(true)に評価されるとは限りません。これは、インライン定義内で定義された静的オブジェクトが、外部定義内の対応するオブジェクトとは異なることを示しています。これにより、このタイプの非constオブジェクトを定義することに対してさえ制約が動機付けられました。

インライン化は、既存のリンカーテクノロジで実装できるように標準に追加され、C99インライン化のサブセットはC++と互換性があります。これは、インライン関数の定義を含む1つの翻訳単位を、その関数の外部定義を提供するものとして指定することを要求することにより達成されました。その仕様は、単にinlineキーワードがないか、inlineexternの両方を含む宣言で構成されるため、C++トランスレーターでも受け入れられます。

C99のインライン展開は、2つの方法でC++仕様を拡張します。まず、関数が1つの変換単位でinlineとして宣言されている場合、他のすべての変換単位でinlineを宣言する必要はありません。これにより、たとえば、ライブラリ関数をライブラリ内でインライン化できますが、他の場所で外部定義を介してのみ使用できます。外部関数にラッパー関数を使用する代わりに、追加の名前が必要です。また、翻訳者が実際にインライン置換を行わないと、パフォーマンスに悪影響を与える可能性があります。

第二に、インライン関数のすべての定義が「まったく同じ」という要件は、プログラムの動作が、呼び出しが可視のインライン定義または外部定義で実装されるかどうかに依存してはならないという要件に置き換えられます。関数。これにより、インライン定義を特定の翻訳単位内での使用に特化できます。たとえば、ライブラリ関数の外部定義には、同じライブラリ内の他の関数からの呼び出しには不要な引数検証が含まれる場合があります。これらの拡張機能にはいくつかの利点があります。また、互換性を懸念するプログラマーは、より厳密なC++ルールに従うだけで済みます。

実装が標準ヘッダーで標準ライブラリ関数のインライン定義を提供することはnotには適切ではないことに注意してください。 inlineキーワードは、関数のインライン化を提案する移植可能な方法をユーザーに提供することのみを目的としています。標準ヘッダーは移植可能である必要がないため、実装には次の行に沿って他のオプションがあります。

#define abs(x) __builtin_abs(x)

または標準ライブラリ関数をインライン化するためのその他の移植性のないメカニズム。

24

>リンカエラーが発生します(fへの未定義の参照)

ここで動作します:Linux x86-64、GCC 4.1.2。コンパイラのバグの可能性があります。指定されたプログラムを禁止する標準の引用段落には何も表示されません。 iff ではなくifを使用していることに注意してください。

インライン定義は、外部定義の代わりにを提供します。これは、トランスレーターmayが同じ変換単位で関数の呼び出しを実装するために使用します。

そのため、関数fの動作がわかっていて、それをタイトループで呼び出す場合は、その定義をモジュールにコピーアンドペーストして、関数呼び出しを防ぐことができます。 or、現在のモジュールの目的上、同等の定義を提供できます(ただし、入力検証、または想像できる最適化はスキップします)。ただし、コンパイラの作成者には、代わりにプログラムサイズを最適化するオプションがあります。

0
Fred Foo