7

好奇心から、インタビューの質問からこのコードを試しました[*]

int main(int argc, char *argv[])
{
    int a = 1234;
    printf("Outer: %d\n", a);
    {
        int a(a);
        printf("Inner: %d\n", a);
    }
}

Linux(g++4.6.3とclang++3.0の両方)でコンパイルすると、次のように出力されます。

Outer: 1234
Inner: -1217375632

ただし、Windows(VS2010)では、次のように出力されます。

Outer: 1234
Inner: 1234

理論的根拠は、2番目の「a」変数のコピーコンストラクターが終了するまで、最初の「a」変数に引き続きアクセスできるということです。ただし、これが標準的な動作なのか、それとも(他の)Microsoftの癖なのかはわかりません。

何か案が?

[*]実際の質問は次のとおりです。

一時変数またはグローバル変数を使用せずに、スコープ内の変数を、包含スコープ内の同じ名前の変数の値で初期化するにはどうすればよいですか?

{
    // Not at global scope here
    int a = 1234;
    {
        int a;
        // how do you set this a to the value of the containing scope a ?
    }
}
4

5 に答える 5

10

一時変数またはグローバル変数を使用せずに、スコープ内の同じ名前の変数の値でスコープ内の変数を初期化するにはどうすればよいでしょうか?

外側のスコープに明示的に名前を付けることができない限り、これを行うことはできません。グローバル スコープ、名前空間スコープ、およびクラス スコープには明示的に名前を付けることができますが、関数またはブロック ステートメントのスコープには名前を付けることができません。


C++11 [basic.scope.pdecl 3.3.2 p1 の状態:

名前の宣言のポイントは、完全な宣言子 (第 8 節) の直後で、初期化子(存在する場合) の前です。ただし、以下に示す場合を除きます。【例:

int x = 12;
{ int x = x; }

ここで、2 番目の x は独自の (不確定な) 値で初期化されます。—終わりの例]

MSVC はこの例を正しく実装していますが、イニシャライザが代入構文の代わりに括弧を使用している場合、これを正しく実装していません。これについては、Microsoft Connect にバグが報告されています。

このバグの結果、VS で正しく動作しないプログラムの例を次に示します。

#include <iostream>

int foo(char) { return 0; }
int foo(int) { return 1; } 

int main()
{
    char x = 'a';
    {
        int x = foo(static_cast<decltype(x)>(0));
        std::cout << "'=' initialization has correct behavior? " << (x?"Yes":"No") << ".\n";
    }
    {
        int x(foo(static_cast<decltype(x)>(0)));
        std::cout << "'()' initialization has correct behavior? " << (x?"Yes":"No") << ".\n";
    }
}

C++ には、次の注記が含まれています。

[注: 不確定な値を含む操作は、未定義の動作を引き起こす可能性があります。—終わりのメモ]

ただし、この注記は、操作によって未定義の動作発生する可能性があることを示しており、必ずしもそうであるとは限りません。上記のリンクされたバグ レポートには、これがバグであり、プログラムが未定義の動作をトリガーするものではないという Microsoft からの確認が含まれています。

編集:そして今、不確定な値を持つオブジェクトが未評価のコンテキストでのみ「使用」されるように例を変更しました。これにより、どのプラットフォームでも未定義の動作の可能性が完全に排除されると信じていますビジュアルスタジオ。

于 2012-05-23T16:28:34.647 に答える
7

一時変数またはグローバル変数を使用せずに、スコープ内の変数を、包含スコープ内の同じ名前の変数の値で初期化するにはどうすればよいですか?

言葉遣いについて技術的に知りたいのなら、それはとても簡単です。「一時的」は、C ++では特定の意味を持ちます(§12.2を参照)。作成する名前付き変数は一時的なものではありません。そのため、正しい値で初期化されたローカル変数(一時変数ではない)を作成できます。

int a = 1234;
{ 
   int b = a;
   int a = b;
}

さらに防御可能な可能性は、外部スコープの変数への参照を使用することです。

int a = 1234;
{ 
    int &ref_a = a;
    int a = ref_a;
}

これにより、余分な変数はまったく作成されません。外部スコープで変数のエイリアスが作成されるだけです。エイリアスの名前が異なるため、変数(一時的またはその他)を定義せずに、外部スコープで変数へのアクセスを保持します。多くの参照は内部でポインターとして実装されていますが、この場合(少なくとも最新のコンパイラーと最適化がオンになっている場合)、エイリアスは実際には変数を参照する別の名前として扱われるとは思われません。外側のスコープで(そしてVC ++での簡単なテストは、それがこのように機能することを示しています-生成されたアセンブリ言語はまったく使用ref_aしません)。

同じ線に沿った別の可能性は次のようになります:

const int a = 10;
{ 
    enum { a_val = a };
    int a = a_val;
}

これは、この場合、変数と呼ぶことができるかどうかについて議論する余地さえないことを除いて、参照にいくらか似ていa_valます-それは絶対に変数ではありません。問題は、列挙型は定数式でしか初期化できないため、それが機能するように外部変数を定義する必要があるconstことです。

これらのどれもがインタビュアーが本当に意図したものであるとは思えませんが、それらはすべて、述べられているように質問に答えます。1つ目は、(確かに)用語の定義に関する純粋な技術です。2番目はまだいくつかの議論に開かれているかもしれません(多くの人々は参照を変数と考えています)。それは範囲を制限しますが、3番目についての質問や議論の余地はありません。

于 2012-05-23T17:44:38.760 に答える
2

変数をそれ自体で初期化することは、未定義の動作です。すべてのテストケースが正しくなりました。これは癖ではありません。実装を初期化aすることもできますが123456789、それは依然として標準です。

更新:この回答のコメントは、それ自体で変数を初期化することは未定義の動作ではなく、そのような変数を読み取ろうとすることを指摘しています。

于 2012-05-23T16:14:19.987 に答える
1

一時変数またはグローバル変数を使用せずに、スコープ内の同じ名前の変数の値でスコープ内の変数を初期化するにはどうすればよいでしょうか?

できません。同一の名前が宣言されるとすぐに、残りのスコープから外側の名前にアクセスできなくなります。外部変数のコピーまたはエイリアスが必要になります。つまり、一時変数が必要になります。

警告レベルを上げても、VC++ が次の行に文句を言わないことに驚いています。

int a(a);

Visual C++ は、変数を非表示にすることについて警告する場合があります (おそらく、派生クラスのメンバーのみ)。また、初期化される前に値を使用していることを伝えることも、通常はかなり良いことです。ここではこれが当てはまります。

生成されたコードを見ると、たまたま内側の a が外側の a と同じ値に初期化されています。これがレジスタに残っているためです。

于 2012-05-23T16:49:29.033 に答える
0

私は標準を見ました、それは実際には灰色の領域ですが、これが私の2セントです...

3.1 宣言と定義 [basic.def]

  1. 宣言は、名前を翻訳単位に導入するか、以前の宣言によって導入された名前を再宣言します。

  2. 宣言は定義であり、... [関連しない場合が続く]

3.3.1 宣言のポイント

  1. 名前の宣言のポイントは、完全な宣言子の直後で、初期化子 (存在する場合) の前です。ただし、以下に示す [自己割り当ての例] を除きます。

  2. 非ローカル名は、それを隠すローカル名の宣言の時点まで表示されたままです。

さて、これが内側の「a」の宣言のポイントであると仮定すると (3.3.1/1)

int a (a);
     ^

外側の 'a' は、内側の 'a' が定義されているポイント (3.3.1/2) まで見えるはずです。

問題は、この場合、3.1/2 によれば、宣言は定義であるということです。これは、内側の 'a' を作成する必要があることを意味します。それまでは、外側の「a」がまだ表示されているかどうかを標準から理解することはできません。VS2010 はそうであると想定し、括弧内にあるものはすべて外側のスコープを参照します。ただし、clang++ と g++ はその行を自己代入のケースとして扱い、未定義の動作を引き起こします。

どちらのアプローチが正しいかはわかりませんが、VS2010 の方が一貫性があることがわかります。内側の 'a' が完全に作成されるまで、外側のスコープは引き続き表示されます。

于 2012-05-24T09:24:12.707 に答える