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


83

ヘッダーファイルで宣言および定義されている関数があります。これはそれ自体が問題です。その関数がインライン化されていない場合、そのヘッダーを使用するすべての変換ユニットは関数のコピーを取得し、それらがリンクされている場合は複製されます。関数をインライン化することで「修正」しましたが、「inline」キーワードを指定してもコンパイラーはインライン化を保証しないため、これは脆弱な解決策ではないかと思います。これが当てはまらない場合は、訂正してください。

とにかく、本当の問題は、この関数内の静的変数はどうなるのかということです。最終的に何部になりますか?

回答:


105

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

静的関数?

関数をstaticと宣言すると、コンパイルユニットで「非表示」になります。

名前空間スコープ(3.3.6)を持つ名前は、次の名前の場合、内部リンケージがあります。

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

3.5 / 3-C ++ 14(n3797)

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

3.5 / 2-C ++ 14(n3797)

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

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

インライン関数?

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

インライン指定子を使用した関数宣言(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であるため、関数を静的として明確にマークしない限り、これはその関数に適用されます)

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

静的ローカル変数?

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

静的+インライン?

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

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

質問を書いたので、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ファイルには、グローバルな非表示変数の独自のプライベートコピーを含む、関数の独自のプライベートコピーがあります。ヘッダーを含むコンパイル単位があります。

内部に「static」変数を含む「static」関数に「inline」を追加します。

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

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

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


リンクフェーズで、インライン関数で宣言されたすべての静的変数が1つに解決されるという重要な点を見逃していると思いますが、間違っていますか?
user14416 2016

1
いいえ、各静的変数は独自の個別の関数内にあるためです。関数の名前は同じですが、内部リンケージがあるため、変換ユニット間で共有されません。
paercebal 2016

1
@paercebal in inline void doSomething() { static int value ; }、関数には外部リンケージがあります; これは、2つの異なるユニットから含まれているヘッダーに表示される場合はODR違反です
MM

@MMどういう意味ですか?あなたの機能はinline、ODRに違反することはできません。
ルスラン

非sequiturだ@Ruslan
MM

39

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

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


2
AFAICT、あなたはここで言っていることは完全に正しいです。なぜ人々がこの答えに反対票を投じているのか理解できません。私の唯一の推測は、彼らが「変数の多くのコピー」まで読んでから停止することです!:(とにかく私からのトークン(1)。
リチャードコーデン

3
オブジェクトファイルを実行できないため、コンパイラとは何を意味するのかと聞かれると、コンパイラ+リンカーを意味します。したがって、この答えは正しいですが、まったく意味がありません。
エヴァンダーク

1
人々は無知だからです。これはより高度な質問であり、すべての人が議論の中で区別することになっています。
ソガルター2012

13

Mark Ransomの答えは役に立ちました。コンパイラは静的変数のコピーを多数作成しますが、リンカは1つを選択し、すべての変換ユニットに適用します。

私がこれを見つけた他の場所:

[dcl.fct.spec] / 4を参照してください

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

確認する規格のコピーはありませんが、VS Express2008でアセンブリを調べた経験と一致しています。


5

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

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

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


1
彼は、静的関数の変数ではなく、「インライン関数」の「静的変数」について質問しています。
リチャードコーデン

私たちはそれに同意しますが、あなたは正しいです。答えを文脈に戻すには編集が必要です。
ラファエルサンピエール

私もこれに出くわしました。それで、2つのうちどちらですか?inline関数がインライン化されますか、それとも複数のコピーがあっても大丈夫ですか?
Vassilis

@Vassilisはどちらも正しいですinlineインライン化は発生しません、それを示唆するだけであり、複数の定義を許可します(ただし、同じコンパイル単位ではできません)。
ラファエルサンピエール

3

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

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

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

本当の問題は、物事がこのようになっているのか、それともこれがMicrosoft C ++コンパイラのイデオシンクロシーなのかということです。


1
「関数が「静的インライン」の場合」-元の投稿では、それについて何も述べられていませんでした。関数の静的は変数の静的とは異なる意味を持つため、異なる結果を期待する必要があります。関数の静的は、他の変換ユニットがこの定義を認識しないことを意味します。
Windowsのプログラマ

設定はわかりませんが、この場合、コンパイラは正しく機能しています。ただし、将来、準拠していないコンパイラに遭遇した場合に備えて、単体テストを含めることをお勧めします。
ロバートグールド

-1

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


-1

設計上の問題に加えて、これはすべて意味する可能性があります。すでに問題が発生しているため、この場合はインラインではなく静的を使用する必要があります。そうすれば、誰もが同じ変数を共有します。(静的関数)


-2

翻訳単位ごとに1つになると思います。その関数(およびその宣言された静的変数)の多くのバージョンが効果的にあり、ヘッダーを含むすべての変換ユニットに1つずつあります。


-2

静的とは、1つのコピーがプログラム全体に分散されることを意味しますが、インラインとは、同じプログラム内で同じコードを複数回必要とすることを意味します。したがって、インライン関数内で変数を静的にすることはできません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.