1. ホーム
  2. c++

[解決済み] インライン関数内の静的変数

2023-03-31 20:51:37

質問

ヘッダーファイルで宣言・定義された関数があります。これはそれ自体で問題です。その関数がインライン化されていない場合、そのヘッダーを使用するすべての翻訳ユニットが関数のコピーを取得し、それらが一緒にリンクされると、重複してしまいます。私はその関数をインライン化することで解決しましたが、私の知る限り、コンパイラは "inline" キーワードを指定してもインライン化を保証しないので、これはもろい解決方法だと思います。もしこれが真実でないなら、訂正してください。

とにかく、本当の疑問は、この関数の内部で静的変数はどうなるのか、ということです。最終的にいくつのコピーを持つことになるのでしょうか。

どのように解決するのですか?

何か見落としているようですね。

静的関数?

関数をstaticと宣言すると、そのコンパイル単位で"hidden"されます。

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

- 変数、関数、関数テンプレートの名前であり、明示的に静的と宣言されている場合。

3.5/3 - C++14 (n3797)

名前が内部連結を持つ場合、それが示す実体は、同じ翻訳ユニット内の他のスコープからの名前によって参照されることができます。

3.5/2 - C++14 (n3797)

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

その関数の中に静的変数がある場合、このヘッダーを含む各コンパイルユニットもまた、彼ら自身の、個人的なバージョンを持つことになるということです。

インライン関数?

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

inline指定子を持つ関数宣言(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とマークしない限り、これはその関数に適用されます)

これは欠点(インライン化されていない場合、せいぜい一度しか存在しない)なしに、"static"(すなわち、ヘッダーで定義できる)の利点を持っています。

静的ローカル変数?

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

static + inline?

インラインとスタティックを混在させると、あなたが説明したような結果になります(関数がインライン化されていても、内部のスタティック変数はそうではなく、スタティック関数の定義を含むコンパイルユニットと同じ数のスタティック変数が存在することになります)。

著者の追加の質問に対する回答

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

関数が単に "inline" である場合、静的変数のコピーは1つだけです。

関数が "static inline" であるとき、翻訳ユニットと同じ数だけコピーが存在します。

本当の問題は、物事がこのようになるはずなのか、それともこれが 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 ;
}

は、内部の静的変数に関する限り、この "inline" キーワードを追加しないのと同じ結果をもたらします。

つまり、VC++ の動作は正しく、あなたは "inline" と "static" の本当の意味を取り違えているのです。