91

ヘッダー ファイルで宣言および定義されている関数があります。これはそれ自体が問題です。その関数がインライン化されていない場合、そのヘッダーを使用するすべての翻訳単位は関数のコピーを取得し、それらがリンクされると複製されます。関数をインラインにすることでそれを「修正」しましたが、「インライン」キーワードを指定した場合でも、私の知る限り、コンパイラはインライン化を保証しないため、これは脆弱なソリューションであると思います。これが正しくない場合は、訂正してください。

とにかく、本当の問題は、この関数内の静的変数に何が起こるかということです。最終的に何部コピーできますか?

4

9 に答える 9

118

ここで何かが足りないと思います。

静的関数?

関数を静的に宣言すると、そのコンパイル単位で「非表示」になります。

名前空間スコープ (3.3.6) を持つ名前は、

— 明示的に static と宣言された変数、関数、または関数テンプレート。

3.5/3 - C++14 (n3797)

名前に内部リンケージがある場合、それが示すエンティティは、同じ翻訳単位内の他のスコープからの名前で参照できます。

3.5/2 - C++14 (n3797)

この静的関数をヘッダーで宣言すると、このヘッダーを含むすべてのコンパイル ユニットに独自の関数のコピーが含まれます。

問題は、その関数内に静的変数がある場合、このヘッダーを含む各コンパイル単位にも独自の個人的なバージョンがあることです。

インライン関数?

インラインで宣言すると、インライン化の候補になります (コンパイラがインライン化するかどうかにかかわらず、キーワード inline が存在するか存在しないかという事実を無視する場合があるため、C++ では最近ではあまり意味がありません)。

インライン指定子を使用した関数宣言 (8.3.5、9.3、11.3) は、インライン関数を宣言します。インライン指定子は、呼び出し時点での関数本体のインライン置換が通常の関数呼び出しメカニズムよりも優先されることを実装に示します。呼び出し時にこのインライン置換を実行する実装は必要ありません。ただし、このインライン置換が省略された場合でも、7.1.2 で定義されたインライン関数の他の規則は引き続き尊重されます。

7.1.2/2 - C++14 (n3797)

ヘッダーでは、興味深い副作用があります。インライン化された関数は同じモジュールで複数回定義でき、リンカーは単に「それら」を 1 つに結合します (コンパイラーの理由でインライン化されていない場合)。

内部で宣言された静的変数の場合、標準では、そのうちの1つだけが明確に示されています。

extern インライン関数の静的ローカル変数は、常に同じオブジェクトを参照します。

7.1.2/4 - C++98/C++14 (n3797)

(関数はデフォルトで extern であるため、関数を static として明示的にマークしない限り、これはその関数に適用されます)

これには、欠陥のない「静的」(つまり、ヘッダーで定義できる)という利点があります(インライン化されていない場合、最大で 1 回存在します)。

静的ローカル変数?

静的ローカル変数にはリンケージがありません (スコープ外の名前で参照することはできません) が、静的な保存期間があります (つまり、グローバルですが、その構築と破棄は特定の規則に従います)。

静的 + インライン?

インラインと静的を混在させると、説明した結果が得られます(関数がインライン化されていても、内部の静的変数はインライン化されず、静的関数の定義を含むコンパイル単位と同じ数の静的変数で終了します) )。

著者の追加の質問への回答

質問を書いたので、Visual Studio 2008 で試してみました。VS が標準に準拠して動作するようにするすべてのオプションをオンにしようとしましたが、一部を見逃している可能性があります。結果は次のとおりです。

関数が単に「インライン」である場合、静的変数のコピーは 1 つだけです。

関数が「静的インライン」の場合、翻訳単位と同じ数のコピーがあります。

本当の問題は、物事がこのように想定されているのか、それともこれが Microsoft C++ コンパイラの特異性なのかということです。

だから私はあなたがそのようなものを持っていると思います:

void doSomething()
{
   static int value ;
}

関数内の static 変数は、簡単に言えば、関数のスコープ以外には隠されているグローバル変数であり、内部で宣言されている関数だけがアクセスできることを意味します。

関数をインライン化しても何も変わりません:

inline void doSomething()
{
   static int value ;
}

非表示のグローバル変数は 1 つだけです。コンパイラがコードをインライン化しようとしても、グローバル隠し変数が 1 つしかないという事実は変わりません。

ここで、関数が静的に宣言されている場合:

static void doSomething()
{
   static int value ;
}

次に、コンパイル単位ごとに「プライベート」です。つまり、静的関数が宣言されているヘッダーを含むすべての CPP ファイルには、グローバル隠し変数の独自のプライベート コピーを含む、関数の独自のプライベート コピーがあるため、変数はヘッダーを含むコンパイル単位があります。

「静的」変数を内部に持つ「静的」関数に「インライン」を追加します。

inline static void doSomething()
{
   static int value ;
}

内部の静的変数に関する限り、この「インライン」キーワードを追加しない場合と同じ結果になります。

したがって、VC++ の動作は正しく、「インライン」と「静的」の本当の意味を誤解しています。

于 2008-10-09T20:51:02.370 に答える
43

コンパイラは変数のコピーを多数作成すると思いますが、リンカは 1 つを選択し、他のすべての変数がそれを参照するようにします。インライン関数のさまざまなバージョンを作成する実験を試みたとき、同様の結果が得られました。関数が実際にインライン化されていない場合 (デバッグ モード)、呼び出し元のソース ファイルに関係なく、すべての呼び出しは同じ関数に行きました。

少しの間、コンパイラーのように考えてみてください。各コンパイル単位 (ソース ファイル) は他から独立しており、個別にコンパイルできます。したがって、それぞれが変数のコピーを作成し、それが唯一のものであると考えなければなりません。リンカーには、これらの境界を越えて到達し、変数と関数の両方の参照を調整する機能があります。

于 2008-10-09T02:35:58.853 に答える
12

Mark Ransom の回答が役立つことがわかりました。コンパイラは静的変数のコピーを多数作成しますが、リンカーは 1 つを選択し、すべての翻訳単位に適用します。

他の場所でこれを見つけました:

[dcl.fct.spec]/4 を参照

[..] 外部リンケージを持つインライン関数は、すべての翻訳単位で同じアドレスを持つ必要があります。extern インライン関数の静的ローカル変数は、常に同じオブジェクトを参照します。extern inline 関数の文字列リテラルは、異なる翻訳単位の同じオブジェクトです。

チェックする標準のコピーはありませんが、VS Express 2008 でアセンブリを調べた経験と一致します。

于 2009-09-05T22:27:13.483 に答える
7

このようになっているはずです。「静的」は、関数をコンパイル単位に対してローカルにすることをコンパイラーに伝えます。したがって、コンパイル単位ごとに 1 つのコピーと、関数のインスタンスごとに静的変数の 1 つのコピーが必要です。

「インライン」は、関数をインライン化する必要があることをコンパイラに伝えるために使用されます。最近では、「コードのコピーが複数あっても問題ありません。同じ機能であることを確認してください」と見なされます。したがって、全員が静的変数を共有します。

注:この回答は、元の投稿者が自分自身に投稿した回答に応じて書かれました。

于 2008-10-09T02:33:24.410 に答える
3

質問を書いたので、Visual Studio 2008 で試してみました。VS が標準に準拠して動作するようにするすべてのオプションを有効にしようとしましたが、一部を見逃している可能性があります。結果は次のとおりです。

関数が単に「インライン」である場合、静的変数のコピーは 1 つだけです。

関数が「静的インライン」の場合、翻訳単位と同じ数のコピーがあります。

本当の問題は、物事がこのように想定されているのか、それともこれが Microsoft C++ コンパイラの特異性なのかということです。

于 2008-10-09T02:19:25.390 に答える
-1

インライン化とは、実行可能コード (命令) が呼び出し関数のコードにインライン化されることを意味します。コンパイラは、ユーザーが要求したかどうかに関係なく、それを行うことを選択できます。これは、関数で宣言された変数 (データ) には影響しません。

于 2008-10-09T01:51:32.233 に答える
-3

翻訳単位ごとに 1 つになると思います。その関数 (およびその宣言された静的変数) の多くのバージョン (ヘッダーを含むすべての翻訳単位に 1 つ) を効果的に取得できます。

于 2008-10-09T01:47:40.963 に答える
-3

静的とは、1 つのコピーがプログラム全体に配布されることを意味しますが、インラインとは、同じプログラム内で同じコードが何度も必要になることを意味するため、インライン関数内で変数を静的にすることはできません。

于 2009-08-29T02:34:12.577 に答える
-4

設計上の問題に加えて、これはすべて暗示される可能性があります。すでにそれに行き詰まっているため、この場合はインラインではなく static を使用する必要があります。そうすれば、全員が同じ変数を共有します。(スタティック機能)

于 2008-10-09T02:15:13.857 に答える