3
inline void foo();

int main() {
    foo(); return 0;
}

void foo(){
}

fooの宣言をの定義inlineではなくとして設定する場合、違いはありますか? foo逆にどうですか?両方を としてマークするのはどうinlineですか?

4

1 に答える 1

4

これはC11がインラインについて述べているすべてです (注:inlineは関数指定子であり、現在、他の唯一の関数指定子は です_Noreturn) (強調は私のものです):

6.7.4

2. 関数指定子は、関数の識別子の宣言でのみ使用されます。

3. 外部リンケージを持つ関数のインライン定義には、静的またはスレッド保存期間を持つ変更可能なオブジェクトの定義が含まれていてはならず、内部リンケージを持つ識別子への参照が含まれていてはなりません。

5. 関数指定子は、複数回出現する場合があります。動作は、一度だけ表示された場合と同じです。

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

7. 内部リンケージを持つ関数は、インライン関数にすることができます。外部リンケージを持つ関数の場合、次の制限が適用されます。関数がインライン関数指定子で宣言されている場合、同じ翻訳単位でも定義される必要があります。 翻訳単位内の関数のすべてのファイル スコープ宣言に extern なしのインライン関数指定子が含まれている場合、その翻訳単位内の定義はインライン定義です。 インライン定義は関数の外部定義を提供せず、別の翻訳単位での外部定義を禁止しません。インライン定義は、翻訳者が同じ翻訳単位内の関数への呼び出しを実装するために使用できる外部定義の代替手段を提供します。関数の呼び出しがインライン定義を使用するか、外部定義を使用するかは指定されていません。

10. 例 1 外部リンケージを持つインライン関数の宣言は、外部定義、または翻訳単位内でのみ使用可能な定義のいずれかになります。extern を使用したファイル スコープ宣言は、外部定義を作成します。次の例は、翻訳単位全体を示しています。

inline double fahr(double t)
{
    return (9.0 * t) / 5.0 + 32.0;
}
inline double cels(double t)
{
    return (5.0 * (t - 32.0)) / 9.0;
}
extern double fahr(double); // creates an external definition
double convert(int is_fahr, double temp)
{
    /* A translator may perform inline substitutions */
    return is_fahr ? cels(temp) : fahr(temp);
}

11.も extern で宣言されてfahrいるため、 の定義は外部定義ですが、 の定義はインライン定義であることに注意してください。外部リンケージがあり、参照されるため、外部定義は別の翻訳単位に表示される必要があります (6.9 を参照)。インライン定義と外部定義は別個のものであり、どちらも呼び出しに使用できます。fahrcelscels

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

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

6.9

5. 外部定義は、関数 (インライン定義以外) またはオブジェクトの定義でもある外部宣言です。

J.1 規定されていない振る舞い

- インライン関数への呼び出しが関数のインライン定義または外部定義を使用するかどうか (6.7.4)。

J.2 未定義の動作

- 外部リンケージを持つ関数がインライン関数指定子で宣言されていますが、同じ翻訳単位でも定義されていません (6.7.4)。

J.3 実装定義の振る舞い

J.3.8 ヒント

- インライン関数指定子を使用して行われた提案が有効である範囲 (6.7.4)。


これが人間にとって何を意味するのか

  • 6.7.4.3: リンカーからも見えるインライン関数がある場合、staticローカル変数を持つことはできません。また、ファイル スコープの変数/関数 (グローバル識別子のように定義されていますが、 を使用static) も使用できません。(注: この最後の文について、私の理解が正しいかどうか誰か教えてもらえますか? 正しくないと感じます。)

  • 6.7.4.6: 関数をインライン化しても、コンパイラがマクロのように動作することは強制されません。関数を高速にしたいということをコンパイラーに示唆するだけです。

  • 6.7.4.7:

    1. static機能がある場合は、それを作成できinlineます。
    2. 翻訳単位 (TU) (たとえば、前処理されたソース ファイル) でコンパイラがinline関数宣言を検出した場合、同じ TU 内のどこかでその定義を見つけることができるはずです。
    3. どこでも配置した関数を宣言した場合inline、コンパイラはその関数をstatic(正確ではないが) のように認識します。
    4. ポイント 3 のような関数が TU で使用される場合、関数は外部リンケージを取得するため、別の実装が他の TU に存在する必要があります。関数のインライン バージョンまたは外部バージョンを使用するかどうかは、コンパイラの選択です。1
  • 138: 異なる TU でインライン関数を何度定義しても、それらは「同じ」アドレスを持ちます。これは、関数ポインターを使用してアドレスを格納し、そのポインターを介して関数を呼び出すと、関数の外部定義が呼び出されることも意味すると思います。

  • 6.9.5: 通常の関数定義は、外部関数の宣言と定義の両方を行います。しかし、前に見たように、関数のインライン定義は、外部関数を宣言する可能性があっても、外部から見える定義を定義しません。

1あなたの例でinlineは、 の定義に配置すると、未定義の参照fooを持つ gcc (リンクフェーズ中) によってエラーが発生します。fooの別の実装をfoo同じファイルに配置すると、再定義エラーが発生します。これは、私が言ったことと一致しています。すべてインラインの宣言/定義は外部リンケージを提供せず、関数が TU で使用される場合、別の TU での定義が必要です。


参考文献:

于 2013-04-02T16:39:22.920 に答える