web-dev-qa-db-ja.com

インライン関数の静的変数

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

とにかく、本当の問題は、この関数内の静的変数はどうなるのですか?いくつのコピーができますか?

69
Dirk Groeneveld

ここに何か欠落していると思います。

静的関数?

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

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

—明示的に静的に宣言されている変数、関数、または関数テンプレート。

3.5/3-C++ 14(n3797)

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

3.5/2-C++ 14(n3797)

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

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

インライン関数?

インラインで宣言すると、インライン化の候補になります(現在のC++では、コンパイラーがインライン化するかどうかに関係なく、キーワードインラインが存在する、または存在しないという事実を無視することがあります)。

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

7.1.2/2-C++ 14(n3797)

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

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

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

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

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

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

静的ローカル変数?

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

静的+インライン?

インラインと静的を混在させると、説明した結果が得られます(関数がインライン化されている場合でも、内部の静的変数はそうではなく、静的関数の定義を含むコンパイルユニットと同じ数の静的変数で終了します) )。

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

質問を書いてから、Visual Studio 2008で試してみました。VSを標準に準拠して動作させるためのすべてのオプションをオンにしようとしましたが、いくつか見落としている可能性があります。これらは結果です:

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

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

本当の問題は今、このようになっているはずなのか、それともMicrosoft C++コンパイラの特異性なのかということです。

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

void doSomething()
{
   static int value ;
}

関数内の静的変数を簡単に言えば、関数のスコープ以外のすべてに隠されたグローバル変数、つまり、関数内で宣言された関数だけがそれに到達できることを理解する必要があります。

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

inline void doSomething()
{
   static int value ;
}

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

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

static void doSomething()
{
   static int value ;
}

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

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

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

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

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

90
paercebal

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

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

36
Mark Ransom

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

他に私がこれを見つけた:

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

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

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

11
Matt

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

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

注:この回答は、元の投稿者が自分に投稿した回答への返信として作成されました。

質問を書いてから、Visual Studio 2008で試してみました。VSを標準に準拠して動作させるためのすべてのオプションをオンにしようとしましたが、いくつか見落としている可能性があります。これらは結果です:

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

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

本当の問題は今、このようになっているのか、それともMicrosoft C++コンパイラの思想同期なのかということです。

3
Dirk Groeneveld