ヘッダーファイルの変数宣言-静的かどうか


91

一部#definesをリファクタリングするときに、C ++ヘッダーファイルで次のような宣言に遭遇しました。

static const unsigned int VAL = 42;
const unsigned int ANOTHER_VAL = 37;

問題は、静的である場合、どのような違いがあるかです。古典的な#ifndef HEADER #define HEADER #endifトリックのため、ヘッダーを複数含めることはできません(重要な場合)。

静的とVALは、ヘッダーが複数のソースファイルに含まれている場合に、1つのコピーのみが作成されることを意味しますか?


回答:


107

staticそこの1コピーされることを意味しVAL、それが中に含まれている各ソースファイル用に作成しました。しかし、それはまた、複数の介在物は、複数の定義にはなりませんことを意味しVALているが、リンク時に衝突します。Cを使用しないstatic場合VAL、他のソースファイルが宣言している間に1つのソースファイルのみが定義されていることを確認する必要がありますextern。通常、これをソースファイルで(おそらくイニシャライザで)定義し、extern宣言をヘッダーファイルに配置することでこれを行います。

static グローバルレベルの変数は、インクルードを介してそこに到達したか、メインファイルに存在したかに関係なく、独自のソースファイルでのみ表示されます。


編集者注: C ++では、宣言にもキーワードconstも含まないオブジェクトは暗黙的にです。staticexternstatic


私は最後の文章のファンであり、信じられないほど役に立ちます。42の方が良いので、私は回答に賛成票を投じませんでした。編集:文法
RealDeal_EE'18

「静的とは、含まれているソースファイルごとにVALのコピーが1つ作成されることを意味します。」これは、2つのソースファイルにヘッダーファイルが含まれている場合、VALのコピーが2つあることを意味しているようです。それが真実ではなく、ヘッダーを含むファイルの数に関係なく、常にVALのインスタンスが1つあることを願っています。
Brent212 14

4
@ Brent212コンパイラは、宣言/定義がヘッダーファイルからのものかメインファイルからのものかを認識しません。だからあなたは無駄に願っています。誰かが愚かで静的な定義をヘッダーファイルに入れ、2つのソースに含まれている場合、VALの2つのコピーが存在します。
Justsalt 2014

1
const値にはC ++で内部リンケージがあります
adrianN

112

staticそしてextern、ファイルスコープ変数のタグは、彼らが(すなわち、他の他の翻訳単位でアクセスしているかどうかを判断する.c.cppのファイル)。

  • static変数の内部リンケージを提供し、他の翻訳単位から隠します。ただし、内部リンケージがある変数は、複数の翻訳単位で定義できます。

  • extern変数の外部リンケージを提供し、他の翻訳単位から見えるようにします。通常、これは、変数を1つの変換単位でのみ定義する必要があることを意味します。

デフォルト(staticまたはを指定しない場合extern)は、CとC ++が異なる領域の1つです。

  • Cでは、ファイルスコープ変数はexternデフォルトで(外部リンケージ)です。Cを使用している場合VALは、is staticANOTHER_VALis externです。

  • C ++では、ファイルスコープの変数はstatic、デフォルトでは(内部リンケージ)constでありextern、そうでない場合はデフォルトです。あなたは、C ++を使用している場合は、両方VALANOTHER_VALしていますstatic

C仕様のドラフトから:

6.2.2識別子のリンケージ... -5-関数の識別子の宣言にストレージクラス指定子がない場合、そのリンケージは、ストレージクラス指定子externで宣言されたかのように正確に決定されます。オブジェクトの識別子の宣言にファイルスコープがあり、ストレージクラス指定子がない場合、そのリンケージは外部です。

C ++仕様のドラフトから

7.1.1-ストレージクラス指定子[dcl.stc] ... -6-ストレージクラス指定子なしで名前空間スコープで宣言された名前は、前の宣言のために内部リンケージがあり、それが提供されていない場合を除き、外部リンケージがありますconstを宣言しました。constと宣言され、externと明示的に宣言されていないオブジェクトには、内部リンケージがあります。


47

静的とは、ファイルごとに1つのコピーを取得することを意味しますが、他のユーザーとは異なり、そうすることは完全に合法であると述べています。小さなコードサンプルでこれを簡単にテストできます:

test.h:

static int TEST = 0;
void test();

test1.cpp:

#include <iostream>
#include "test.h"

int main(void) {
    std::cout << &TEST << std::endl;
    test();
}

test2.cpp:

#include <iostream>
#include "test.h"

void test() {
    std::cout << &TEST << std::endl;
}

これを実行すると、次の出力が得られます。

0x446020
0x446040


5
例をありがとう!
Kyrol

場合、私は疑問TESTだったconstLTOは、単一のメモリ位置にそれを最適化することができるならば、。しかし-O3 -flto、GCC 8.1ではそうではありませんでした。
Ciro Santilli郝海东冠状病六四事件法轮功

そうすることは違法です-たとえそれが一定であっても、staticは各インスタンスがコンパイルユニットに対してローカルであることを保証します。ただし、定数として使用した場合、おそらく定数値自体をインライン化できますが、そのアドレスを取得するため、一意のポインターを返す必要があります。
slicedlime

6

constC ++の変数には内部リンケージがあります。したがって、使用しstaticても効果はありません。

ああ

const int i = 10;

one.cpp

#include "a.h"

func()
{
   cout << i;
}

two.cpp

#include "a.h"

func1()
{
   cout << i;
}

これがCプログラムの場合、i(外部リンケージが原因で)「多重定義」エラーが発生します。


2
まあ、それを使うstaticことはそれが何をコーディングしているかの意図と認識をきちんと合図するという効果を持っています、それは決して悪いことではありません。私にとって、これはvirtualオーバーライドするときに含めるようなものです:必ずしもそうする必要はありませんが、そうすると、物事ははるかに直感的になり、他の宣言と一致します。
underscore_d 2016

Cで複数の定義エラーが発生する可能性があります。これは未定義の動作であり、診断は必要ありません
MM

5

このコードレベルでの静的宣言は、variabelが現在のコンパイルユニットでのみ表示されることを意味します。これは、そのモジュール内のコードのみがその変数を参照することを意味します。

変数staticを宣言するヘッダーファイルがあり、そのヘッダーが複数のC / CPPファイルに含まれている場合、その変数はそれらのモジュールに対して「ローカル」になります。ヘッダーが含まれるN個の場所について、その変数のN個のコピーがあります。それらは互いに全く関係がありません。これらのソースファイル内のコードは、そのモジュール内で宣言された変数のみを参照します。

この特定のケースでは、「static」キーワードは何の利点も提供していないようです。何かが足りないかもしれませんが、それは問題ではないようです-これまでにこのようなことをしたことは一度もありません。

インライン化に関しては、この場合、変数はおそらくインライン化されますが、それはconstと宣言されているからです。コンパイラーモジュールの静的変数をインライン化する可能が高くなりますが、状況とコンパイルされるコードによって異なります。コンパイラが「静的」をインライン化する保証はありません。


ここで「静的」の利点は、それ以外の場合は、ヘッダーを含む各モジュールに1つずつ、すべて同じ名前で複数のグローバルを宣言することです。リンカが文句を言わないのは、それが舌を噛んで礼儀正しくしているからです。

この場合、のためconststaticは暗黙的であり、したがってオプションです。必然的に、マイクFが主張したように、複数の定義エラーの影響を受けません。
underscore_d 2016


2

質問に答えるために、「静的は、ヘッダーが複数のソースファイルに含まれている場合、VALのコピーが1つだけ作成されることを意味しますか?」...

いいえ。VALは常に、ヘッダーを含むすべてのファイルで個別に定義されます。

CとC ++の標準では、この場合に違いが生じます。

Cでは、ファイルスコープの変数はデフォルトでexternです。Cを使用している場合、VALは静的で、ANOTHER_VALはexternです。

現代のリンカーは、ヘッダーが異なるファイルに含まれている場合(同じグローバル名が2回定義されている場合)にANOTHER_VALについて文句を言う可能性があり、ANOTHER_VALが別のファイルで異なる値に初期化された場合は間違いなく文句を言うことに注意してください。

C ++では、ファイルスコープの変数は、constの場合はデフォルトで静的であり、そうでない場合はデフォルトでexternです。C ++を使用している場合、VALとANOTHER_VALはどちらも静的です。

また、両方の変数がconstに指定されているという事実も考慮する必要があります。理想的には、コンパイラーは常にこれらの変数をインライン化し、それらのストレージを含めないことを選択します。ストレージを割り当てることができる理由はたくさんあります。私が考えられるもの...

  • デバッグオプション
  • ファイルに取り込まれたアドレス
  • コンパイラは常にストレージを割り当てます(複雑なconst型は簡単にインライン化できないため、基本型の特殊なケースになります)

注:抽象マシンでは、ヘッダーを含む個別の変換単位ごとにVALのコピーが1つあります。実際には、リンカーはとにかくそれらを組み合わせることに決め、コンパイラーはそれらの一部またはすべてを最初に最適化する場合があります。
MM

1

これらの宣言がグローバルスコープにある(つまり、メンバー変数ではない)と仮定すると、次のようになります。

staticは「内部リンケージ」を意味します。この場合、それはconstと宣言されているため、コンパイラーによって最適化/インライン化できます。constを省略した場合、コンパイラーは各コンパイル単位にストレージを割り当てる必要があります。

staticを省略すると、リンケージはデフォルトでexternになります。繰り返しますが、あなたはconst ness によって救われました-コンパイラは使用を最適化/インライン化できます。constをドロップすると、リンク時に多重定義シンボルエラーが発生します。


別のモジュールは常に「extern const int何か;何か(&whatever);」と言うことができるので、コンパイラはすべての場合にconst intにスペースを割り当てる必要があると思います。

1

静的変数も定義せずに宣言することはできません(これは、ストレージクラス修飾子のstaticとexternが相互に排他的であるためです)。静的変数はヘッダーファイルで定義できますが、これにより、ヘッダーファイルを含む各ソースファイルに変数の独自のプライベートコピーが作成されますが、これはおそらく意図したものではありません。


「しかし、これにより、ヘッダーファイルを含む各ソースファイルに変数の独自のプライベートコピーが作成されますが、これはおそらく意図したものではありません。」- 静的な初期化順序の失敗のため、各翻訳単位にコピーが必要になる場合があります。
jww 2017年

1

const変数は、デフォルトではC ++では静的ですが、extern Cです。したがって、C ++を使用する場合、これはどの構成を使用しても意味がありません。

(7.11.6 C ++ 2003、およびApexndix Cにはサンプルがあります)

コンパイル/リンクソースをCおよびC ++プログラムとして比較する例:

bruziuz:~/test$ cat a.c
const int b = 22;
int main(){return 0;}
bruziuz:~/test$ cat b.c
const int b=2;
bruziuz:~/test$ gcc -x c -std=c89 a.c b.c
/tmp/ccSKKIRZ.o:(.rodata+0x0): multiple definition of `b'
/tmp/ccDSd0V3.o:(.rodata+0x0): first defined here
collect2: error: ld returned 1 exit status
bruziuz:~/test$ gcc -x c++ -std=c++03 a.c b.c 
bruziuz:~/test$ 
bruziuz:~/test$ gcc --version | head -n1
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.5) 5.4.0 20160609

そこの意味はまだ含めてでstatic。これは、プログラマーが行っていることの意図/認識を示し、暗黙的なを欠く他のタイプの宣言(およびfwiw、C)との同等性を維持しstaticます。これは、オーバーライドする関数の宣言を含めvirtual、最近override宣言に含めるようなものです。必須ではありませんが、より多くの自己文書化があり、後者の場合は静的分析に役立ちます。
underscore_d 2016

私は全く同意します。たとえば、私は実際の生活では常に明示的に書いています。
bruziuz

「つまり、C ++を使用する場合、どの構文を使用しても意味がありません...」 -うーん... constヘッダーの変数でのみ使用されるプロジェクトをでコンパイルしたところですg++ (GCC) 7.2.1 20170915 (Red Hat 7.2.1-2)。その結果、約150の多重定義されたシンボルが生成されました(ヘッダーが含まれる各変換単位に1つ)。外部リンケージを回避するにはstaticinlineか、匿名/名前のない名前空間が必要だと思います。
jww 2017年

const intネームスペーススコープ内とグローバルネームスペースで宣言して、gcc-5.4でbaby-exampleを試しました。。そして、それはルール「オブジェクトはconstの宣言と明示的にextern内部リンケージを持って宣言されていない」」......たぶん、プロジェクトに何らかの理由でルールが全く異なるCコンパイルされるソースの中に含まれ、このヘッダをコンパイルし、従わだ
bruziuz

@jww私はCのリンケージの問題とC ++の問題がない例をアップロードしました
bruziuz '15年

0

Staticは、別のコンパイルユニットがその変数をexternするのを防ぎ、コンパイラーが変数の値を使用する場所に単に「インライン化」し、そのためのメモリストレージを作成しないようにします。

2番目の例では、コンパイラーは他のソースファイルがそれをexternしないと想定できないため、実際にその値をメモリーのどこかに格納する必要があります。


-2

Staticは、コンパイラが複数のインスタンスを追加するのを防ぎます。これは#ifndef保護ではあまり重要ではなくなりますが、ヘッダーが2つの別個のライブラリーに含まれ、アプリケーションがリンクされているとすると、2つのインスタンスが含まれます。


「ライブラリ」とは、翻訳単位を意味すると想定します。そうでない場合、インクルードガードは複数の定義を防ぐためにまったく何もせず同じ翻訳単位内での繰り返しのインクルードを防ぐだけであると見なします。したがって、彼らはstatic「重要度を下げる」ために何もしません。そして、両方とも、おそらく意図しない複数の内部的にリンクされた定義になる可能性があります。
underscore_d 2016
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.