いくつかの#defines
をリファクタリングするときに、C++ヘッダーファイルで次のような宣言に遭遇しました。
static const unsigned int VAL = 42;
const unsigned int ANOTHER_VAL = 37;
問題は、もしあるとすれば、どのような違いがあるのでしょうか?古典的な#ifndef HEADER
#define HEADER
#endif
トリック(重要な場合)のため、ヘッダーを複数含めることはできません。
ヘッダーが複数のソースファイルに含まれている場合、静的はVAL
のコピーが1つだけ作成されることを意味しますか?
static
は、含まれるソースファイルごとにVAL
のコピーが1つ作成されることを意味します。ただし、複数のインクルードがVAL
の複数の定義にならないことも意味します。リンク時に衝突します。 Cでは、static
なしで、1つのソースファイルのみがVAL
を定義し、他のソースファイルがextern
を宣言したことを確認する必要があります。通常は、ソースファイルで(おそらく初期化子を使用して)定義し、ヘッダーファイルにextern
宣言を配置することでこれを行います。
グローバルレベルのstatic
変数は、インクルードを介してそこに到達したか、メインファイルに存在するかに関係なく、独自のソースファイルでのみ表示されます。
編集者注: C++では、const
もstatic
キーワードも宣言にないextern
オブジェクトは、暗黙的にstatic
です。
ファイルスコープ変数のstatic
およびextern
タグは、他の翻訳単位(つまり、他の.c
または.cpp
ファイル)でアクセスできるかどうかを決定します。
static
は、変数内部リンケージを提供し、他の翻訳単位から隠します。ただし、内部リンケージを持つ変数は、複数の翻訳単位で定義できます。
extern
は変数に外部リンケージを提供し、他の翻訳単位から見えるようにします。通常、これは、変数を1つの翻訳単位でのみ定義する必要があることを意味します。
デフォルト(static
またはextern
を指定しない場合)は、CとC++が異なる領域の1つです。
Cでは、ファイルスコープの変数はデフォルトでextern
(外部リンケージ)です。 Cを使用している場合、VAL
はstatic
で、ANOTHER_VAL
はextern
です。
C++では、ファイルスコープの変数は、static
である場合はデフォルトでconst
(内部リンケージ)であり、そうでない場合はデフォルトでextern
です。 C++を使用している場合、VAL
とANOTHER_VAL
は両方ともstatic
です。
C仕様 のドラフトから:
6.2.2識別子のリンク... -5-関数の識別子の宣言にストレージクラス指定子がない場合、そのリンケージは、ストレージクラス指定子externで宣言された場合とまったく同じように決定されます。オブジェクトの識別子の宣言にファイルスコープがあり、ストレージクラス指定子がない場合、そのリンケージは外部です。
C++仕様 のドラフトから:
7.1.1-ストレージクラス指定子[dcl.stc] ... -6- storage-class-specifierのないネームスペーススコープで宣言された名前は、前の宣言のために内部リンケージがない限り、外部リンケージを持ちます。宣言されたconst constとして宣言され、externとして明示的に宣言されていないオブジェクトには、内部リンケージがあります。
静的とは、ファイルごとに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
C++のconst
変数には内部リンケージがあります。したがって、static
を使用しても効果はありません。
a.h
const int i = 10;
one.cpp
#include "a.h"
func()
{
cout << i;
}
two.cpp
#include "a.h"
func1()
{
cout << i;
}
これがCプログラムである場合、i
の '複数定義'エラーが発生します(外部リンクのため)。
このレベルのコードでの静的宣言は、変数が現在のコンパイル単位でのみ表示されることを意味します。これは、そのモジュール内のコードのみがその変数を見るということです。
変数を静的に宣言するヘッダーファイルがあり、そのヘッダーが複数のC/CPPファイルに含まれている場合、その変数はそれらのモジュールに対して「ローカル」になります。ヘッダーが含まれるN個の場所について、その変数のN個のコピーがあります。それらは互いにまったく関係していません。これらのソースファイル内のコードは、そのモジュール内で宣言されている変数のみを参照します。
この特定のケースでは、「静的」キーワードは利点を提供していないようです。私は何かを見逃しているかもしれませんが、それは問題ではないようです-私は以前にこのようなことをしたことを見たことがありません。
インライン化に関しては、この場合、変数はインライン化される可能性がありますが、それはconstが宣言されているためです。コンパイラmightはモジュールの静的変数をインライン化する可能性が高くなりますが、それは状況とコンパイルされるコードに依存します。コンパイラが「静的」をインライン化する保証はありません。
質問に答えるには、「ヘッダーが複数のソースファイルに含まれている場合、静的はVALのコピーが1つだけ作成されることを意味しますか?」...
[〜#〜] no [〜#〜]。 VALは、ヘッダーを含むすべてのファイルで常に個別に定義されます。
CとC++の標準は、この場合に違いを引き起こします。
Cでは、ファイルスコープの変数はデフォルトでexternです。 Cを使用している場合、VALは静的で、ANOTHER_VALはexternです。
モダンリンカーは、ヘッダーが異なるファイルに含まれている場合(同じグローバル名が2回定義されている場合)、ANOTHER_VALについて文句を言う可能性があります。
C++では、ファイルスコープの変数は、constの場合はデフォルトで静的であり、そうでない場合はデフォルトでexternです。 C++を使用している場合、VALとANOTHER_VALは両方とも静的です。
また、両方の変数がconstに指定されているという事実も考慮する必要があります。理想的には、コンパイラは常にこれらの変数をインライン化することを選択し、それらのストレージを含めないようにします。ストレージを割り当てることができる理由は、ホスト全体にあります。私が考えることができるもの...
Cの本(無料オンライン)には、リンケージに関する章があり、「静的」の意味をより詳細に説明しています(ただし、正しい答えは他のコメントで既に与えられています): http://publications.gbdirect.co .uk/c_book/chapter4/linkage.html
静的変数も定義せずに宣言することはできません(これは、ストレージクラス修飾子staticとexternが相互に排他的であるためです)。静的変数はヘッダーファイルで定義できますが、これにより、ヘッダーファイルを含む各ソースファイルが変数の独自のプライベートコピーを持つことになりますが、これはおそらく意図したものではありません。
これらの宣言がグローバルスコープにある(つまり、メンバー変数ではない)と仮定すると、
staticは「内部リンケージ」を意味します。この場合、constと宣言されているため、これはコンパイラーによって最適化/インライン化できます。 constを省略すると、コンパイラーは各コンパイル単位にストレージを割り当てる必要があります。
staticを省略すると、デフォルトでリンケージはexternになります。繰り返しますが、const nessによって保存されています-コンパイラーは、最適化/インライン使用が可能です。 constをドロップすると、リンク時にmultiply defined symbolsエラーが発生します。
const変数はC++ではデフォルトで静的ですが、Cはexternです。したがって、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は、別のコンパイルユニットがその変数を外部に格納することを防ぎ、コンパイラが変数の値を使用する場所で「インライン化」し、そのためのメモリストレージを作成しないようにします。
2番目の例では、コンパイラは他のソースファイルが外部にないことを想定できないため、実際にその値をメモリのどこかに格納する必要があります。