「静的」または「extern」のない「インライン」は、C99でこれまで有用でしたか?


95

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

inline void f() {}

int main()
{
    f();
}

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

gcc -std=c99 -o a a.c

リンカエラーが発生します(への未定義の参照f)。エラーは、単にの代わりにstatic inlineまたはを使用した場合、またはコンパイルした場合に消滅します(そのため、関数は実際にはインライン化されています)。extern inlineinline-O

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

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

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

この振る舞いは完全に行き詰まっていませんか?C99 inlineなしで、staticまたはexternC99で関数を定義することは、これまでに有用ですか?何か不足していますか?

回答のまとめ

もちろん、私は何かを逃していた、そしてふるまいはふざけない。:)

以下のようニモを説明し、アイデアが入れてある定義関数のを

inline void f() {}

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

extern inline void f();

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

以下のようジョナサンの答えの中で引用されたC99委員会の根拠 explicates、inlineコールのサイトに表示されるように関数の定義が必要なコンパイラの最適化についてのすべてです。これは、ヘッダーに定義を配置することによってのみ達成できます。もちろん、ヘッダー内の定義は、コンパイラーによって認識されるたびにコードを発行してはなりません。しかし、コンパイラーは実際に関数をインライン化する必要がないため、外部定義がどこかに存在している必要があります。




@Earlz:リンクをありがとう。私の質問は、標準の引用された段落の根拠程度であり、存在する場合、これまでのためのケースを使用inlineせずにstaticしてexternいるが、。残念ながら、これらの問題はいずれもその質問ではカバーされていません。
Sven Marnach

@Nemo:私が投稿する前に、その質問と回答を読みました。繰り返しますが、「どのように動作するのですか?」むしろ「この振る舞いの背後にあるアイデアは何ですか」?私はここで何かを逃していると確信しています。
Sven Marnach、2011年

1
はい、それが私が「境界線」と言った理由です。私は個人的に、これはまったく異なる質問をしていると思います
Earlz

回答:


40

実際、この素晴らしい答えはあなたの質問にも答えると思います:

extern inlineは何をしますか?

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

[更新、詳しく説明]

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


1
おかげで、私はアイデアを得始めています!
Sven Marnach

8
ええ、私は今までこれを自分で理解したことがありませんでした。非EXTERN「インライン」という点でそれは奇妙で定義はヘッダに進む(必ずしも全てで任意のコード生成をもたらさない)「のexternインライン」ながら、宣言が .Cファイルに進み、実際にコードを引き起こします生成されます。
Nemo

3
これinline void f() {}は、ヘッダーとextern inline void f();.cファイルに書き込むことを意味しますか?したがって、実際の関数定義はヘッダーに含まれ、.cファイルには通常の順序とは逆の単なる宣言が含まれていますか?
Sven Marnach、2011年

1
@endolith:私はあなたの質問を理解していません。我々は議論されinline ずに staticextern。もちろんstatic inline大丈夫ですが、それはこの質問と答えがそうであるものではありません。
Nemo

2
@MatthieuMoyの-std=c99代わりに使用する必要があります-std=gnu89
a3f 2017

27

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

付録J.2未定義の動作

  • ...
  • 外部結合を持つ機能で宣言されたinline関数の指定だけでなく、同一の翻訳単位(6.7.4)で定義されていません。
  • ...

C99委員会はRationaleを作成し、次のように述べています。

6.7.4関数指定子

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

関数に内部リンケージがある場合、または関数に外部リンケージがあり、呼び出しが外部定義と同じ変換単位内にある場合、可視性が保証されます。これらの場合、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(真)に評価されることが保証されていません。これは、インライン定義内で定義された静的オブジェクトが、外部定義内の対応するオブジェクトとは異なることを示しています。これconstは、このタイプの非オブジェクトを定義することに対する制約を動機づけました。

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

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

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

標準ヘッダーに標準ライブラリ関数のインライン定義を提供することは実装に適していません。これは、ヘッダーを含めた後に標準ライブラリ関数を再宣言するレガシーコードを破壊する可能性があるためです。このinlineキーワードは、関数のインライン化を提案する移植可能な方法をユーザーに提供することのみを目的としています。標準ヘッダーは移植可能である必要はないため、実装には次のような他のオプションがあります。

#define abs(x) __builtin_abs(x)

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


おかげで、これは非常に詳細であり、標準そのものと同じくらい読みやすいです。:-)私は何かが欠けているに違いないことを知っていました。これをどこから入手したのかを教えていただけますか?
スヴェンマルナッハ、2011年

グーグルを使用してリンクを見つけることができるということを思いついただけ
Sven Marnach

@Sven:私はあなたの検索からURLを借りて、それを答えに入れました。私が使用したドキュメントは、以前に(2005年に)保存した根拠のコピーでしたが、これもV5.10でした。
ジョナサンレフラー、2011年

4
再度、感謝します。私が答えから得た最も貴重な洞察は、C99標準の理論的根拠を含むドキュメントがあることです(おそらく他の標準のドキュメントもあるでしょう)。ニモの答えは私に魚を与えたが、これは私に魚を教えた。
Sven Marnach

0

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

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

インライン定義は、外部定義の代替手段を提供します。これは、トランスレーター同じ翻訳単位内の関数への呼び出しを実装するために使用できます。

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


2
標準では、コンパイラーは常に同じ名前の外部関数があると想定し、インラインバージョンの代わりにそれを呼び出すと述べているため、コンパイラーのバグではないと思います。gcc 4.3、4.4、4.5で試したところ、すべてリンカーエラーが発生しました。
スヴェンマルナッハ、2011年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.