web-dev-qa-db-ja.com

関数内で外部変数を初期化するとエラーが発生するのはなぜですか?

このコードはうまくコンパイルされます:

extern int i = 10;

void test()
{
    std::cout << "Hi" << i << std::endl;
}

このコードはエラーを出しますが:

void test()
{
    extern int i = 10;
    std::cout << "Hi" << i << std::endl;
}

エラー:「i」には「extern」と初期化子の両方があります

これを C++ Primer で読みます:

明示的な初期化子を含む宣言はすべて定義です。 externとして定義された変数にイニシャライザを提供できますが、そうすると、externがオーバーライドされます。初期化子を持つexternは定義です。 関数内のexternに初期化子を指定するとエラーになります

同じことがグローバルスコープで許可されているのに、関数でローカルに実行した場合にエラーになる理由を誰かが説明できますか?

27
Amit Tomar

関数内で外部変数を定義しても意味がないのは、次の理由からです。

シンボルexternを宣言するときは、この値のこのようなすべての発生を同じシンボルにリンクするようコンパイラーに指示しています。 extern int iの発生。プログラムでは、外部で定義されたiにリンクします。この例を見てください:

#include <iostream>
using namespace std;

extern int i;
int i = 10;
void test()
{
    std::cout << "Hi" << i << std::endl;
}

int main()
{
    extern int i;
    i++;
    test();
}

この例では、hi11が出力されます。ただし、main内でexternを削除すると、10が出力されます。これは、externがないと、iはグローバルiにリンクせず、iのローカルコピーを作成するためです。

関数内でextern iを定義しても意味がないのは、関数にiを「定義」させるとどうなるかです。どの関数が最初に実行されますか?それはいつ定義されますか?

次の例が有効であると仮定すると、出力はどうなるでしょうか???

#include <iostream>
using namespace std;

extern int i;
int i = 10;
void test()
{
    std::cout << "Hi" << i << std::endl;
}

void test2() {
    extern int i = 1000;
    std::cout<< "HI" << i << std::endl;
}

void test3() {
    extern int i;
    i = 1000;
    std::cout<< "HI" << i << std::endl;
}

int main()
{
    extern int i;
    i++;
    test();
    i = 0;
    test2();
}

Test2の出力は0または1000のどちらにする必要がありますか?私のtest3も見てください。ここでは簡潔に言っています。私のiを外部定義のiにリンクし、その値に1000を割り当てます。これは、値を「初期化」しようとすることとは大きく異なります。

つまり、extern変数は実際にはグローバルとしてのみ意味があり、グローバルスコープで定義する必要があります。あなたの例では、最初のバージョンは私にとってもコンパイルされません。これは面白いと思います。これが簡潔に定義されているかどうか、またはコンパイラが追加の保護を追加するように設計された方法でこれを処理しているかどうかを確認するには、標準のドキュメントを確認する価値があります...

18
ChrisCM

宣言にイニシャライザを追加すると、グローバル変数の定義になります。これは、externなしの同じ定義に相当します。これは、ブックが「外部をオーバーライドする」と言っているときの本の意味です。

グローバル変数は関数内で(externを使用して)宣言できますが、名前空間スコープでのみ定義できます。そのため、2番目のスニペットはエラーです。

Cの設計者が(なぜこれらのルールがC++に導入されたのか)なぜ宣言を許可するがここでは定義を許可しないことを選択したのかを知りたい場合は、言語の履歴を詳しく知ることができないので残念です。

7
Mike Seymour

最初に、リンケージの概念と外部リンケージの意味を理解しておく必要があります。

別のスコープの宣言によって導入された名前と同じオブジェクト、参照、関数、型、テンプレート、名前空間、または値を表す可能性がある場合、名前はlinkageを持っていると言います。

名前にexternalリンケージがある場合、それが示すエンティティは、他の翻訳単位のスコープまたは同じ翻訳単位の他のスコープからの名前で参照できます。
-3.5.6.2 n3242

staticexternとは異なるexternの関数は単なるリクエストであり、staticはコマンドです。

ブロックスコープで宣言された関数の名前と、ブロックスコープのextern宣言で宣言された変数の名前には、リンケージがあります。

  • 同じ名前とタイプのリンケージを持つエンティティの可視宣言があり、最も内側の外側のネームスペーススコープの外側で宣言されたエンティティを無視すると、ブロックスコープ宣言はその同じエンティティを宣言し、前の宣言のリンケージを受け取ります。
  • そのような一致するエンティティが複数ある場合、プログラムの形式は正しくありません。
  • それ以外の場合、一致するエンティティが見つからない場合、ブロックスコープエンティティは外部リンクを受け取ります。

-3.5.6.6 n3242

したがって、ブロックスコープでは、以下の手順を実行することをお勧めします。

     extern int i;//declare it,request the linkage according to 3.5.6.6 above
     i = 10;//modify it when has link to a defination

グローバルextern宣言はおそらく変換形式です

     extern int i =10;

     extern int i;//include in .hpp is recommended 
     int i =10;//global or namespace variable defination
4
yuan

それを置く最も簡単な方法:

externキーワードの目的は、オブジェクトを定義せずに宣言することです。それを定義することで、基本的にコンパイラーに「値を割り当てないで、値を割り当てる」ことを伝えます。これは意味がありません-またはの内部では絶対に行わないでください。ほとんどのコンパイラーは警告を表示して続行するか、コンパイラーをまったく実行せずにエラーを出します。

/externの詳細を説明することはこの質問の範囲を超えていますが、-の回答を読むと役立つ場合があります- この質問

0
Super Cat

extern変数は、関数が実行される前に初期化されます: en.cppreference.com/w/cpp/language/initialization#Non-local_variables

関数ブロック内でstaticではなくexternと宣言された場合でも、static storage durationですが、その 'linkageは、その関数に対してlocalとなるexternal。そのため、実行が関数内のその行を最初に実行するときに初期化されます: en.cppreference.com/w/cpp/language/storage_duration#Static_local_variables

したがって、関数ブロックでstatic変数を初期化することはできますが、extern変数を初期化することはできません。

0
Don Slowik