3

for ループ内で String^ 変数を宣言するが初期化しない C++/CLI プロジェクトがあります。最初の繰り返しで、変数は何らかの値に設定されます。後続の各反復では、前の値が保持されているように見えます。ローカル スコープの変数は、ループのたびに null (または同等のもの) に初期化されるべきではありませんか? これは int でも起こります。また、警告レベルを W4 に設定しない限り、コンパイラは初期化されていない可能性のある値を警告しません。

これは、動作を示すサンプル コードです。

#include "stdafx.h"
using namespace System;

int main(array<System::String ^> ^args)
{
    for(int n = 0; n < 10; n++)
    {
        String^ variable;
        int x;

        switch(n)
        {
        case 1:
            variable = "One";
            x = 1;
            break;
        case 5:
            variable = "Five";
            x = 5;
            break;
        }

        Console::WriteLine("{0}{1}", variable, x);
    }
}

これの出力は次のようになります

One, 1
One, 1
One, 1
One, 1
Five, 5
Five, 5
Five, 5
Five, 5
Five, 5

ローカル スコープの変数を初期化する方法を完全に誤解していますか? これはマネージ C++ に固有の "機能" ですか? これを C# に変換すると、基本警告レベルであっても、コンパイラは両方の変数について警告します。

4

2 に答える 2

1

免責事項: 私は C と C++ をよく知っています。C++/CLI はそれほど多くありません。しかし、あなたが見ている動作は、C または C++ の同様のプログラムに期待するものと本質的に同じです。

String^へのハンドルで、C または C++のポインターStringに似ています。

C++/CLI がハンドルの初期化に新しい規則を追加しない限り、明示的な初期化を行わない型のブロック スコープ変数は、String^最初はガベージ値を持ち、そのメモリ チャンクにたまたまあったものから構成されます。

{ループの反復ごとに、との間に定義された変数が概念的に作成および破棄されます}。そして、反復ごとにローカル変数が同じメモリ位置に割り当てられる可能性があります (これは必須ではありませんが、そうしない本当の理由はありません)。コンパイラは、関数へのエントリでメモリを割り当てるコードを生成することさえできます。

したがって、ループの最初の繰り返しで、variable"One"(というより、 を参照するハンドルに"One") 設定されます。これが によって出力される値Console::WriteLineです。問題ありません。

2 回目の反復でvariableは、最初の反復で使用されたのと同じメモリ位置に割り当てられます。新しい値は割り当てられないため、最初の反復でそのメモリ位置に格納された値が保持されます。同じことが起こりxます。

以前の値が保持されているとは期待できず、プログラムの動作は未定義です。間違ったプログラムがどのように動作するかを理解することではなく、正しく動作するプログラムを作成することが目標である場合、解決策は、すべての変数が使用される前に適切に初期化されるようにすることです。

最初の反復ではなく 2 番目の反復で最初の代入を行った場合、プログラムは最初の反復でクラッシュする可能性がありますが、それは保証されていません。

コンパイラがこれについて警告しない理由については、わかりません。コンパイラのバグを提案するのはためらいますが、これはバグの可能性があります。

また、高い警告レベルが有効になっている場合でも、初期化されていない変数に関する警告には、デフォルトでは実行されない可能性がある制御フロー分析が必要です。警告と高レベルの最適化の両方を有効にすると、 と の両方について警告するのに十分な情報がコンパイラに提供される場合がvariableありxます。

についてでxはなくについて警告するのは、まだ奇妙に思えます。variableW4

于 2012-12-14T18:17:57.870 に答える
0

C++/CLI は標準 C++の拡張/スーパーセットにすぎないため、その仕様のほとんどに準拠し、CLI (~.Net) 要件に適合するように拡張するだけです。

ローカル スコープの変数は、ループのたびに null (または同等のもの) に初期化されるべきではありませんか?

私の知る限り、C++ 標準では、ローカル ループ変数を初期化する方法が定義されていません。

したがって、オーバーヘッドを回避するために、コンパイラは通常、ループに特定のローカル メモリ管理を使用しません。この SO の質問を参照してください: ループ内で変数を宣言するためのオーバーヘッドはありますか? (C++)

ローカル スコープの変数を初期化する方法を完全に誤解していますか?

これはマネージ C++ に固有の "機能" ですか?

いいえ、これは機能や特別な動作ではありません。C++/CLI コンパイラは標準の C++ プラクティスのみを使用しています。

これを C# に変換すると、基本警告レベルであっても、コンパイラは両方の変数について警告します。

C#とAFAIK Javaは、未定義の動作を回避しようとするため、使用する前にローカル変数を初期化する必要があります.

これがコンパイルの結果の CIL です (この一連のテキストを理解しやすくするために、書式設定とコメントを行いました :)):

.locals init (int32 V_0, int32 V_1, string V_2, int32 V_3)
//                   ^          ^           ^          ^
//                   n          x        variable     tmp

// initialization of "n"
IL_0000:  ldc.i4.0
IL_0001:  stloc.0
IL_0002:  br.s       IL_0008

// loop starts here

// post iteration processing
IL_0004:  ldloc.0
IL_0005:  ldc.i4.1
IL_0006:  add
IL_0007:  stloc.0

// stop condition check
IL_0008:  ldloc.0
IL_0009:  ldc.i4.s   10
IL_000b:  bge.s      IL_003e

// initialization of temporary "tmp" variable for switch
IL_000d:  ldloc.0
IL_000e:  stloc.3

// check if "tmp" is 3
IL_000f:  ldloc.3
IL_0010:  ldc.i4.1
// if so go to "variable" intialization
IL_0011:  beq.s      IL_0019

// check if "tmp" is 5
IL_0013:  ldloc.3
IL_0014:  ldc.i4.5
IL_0015:  beq.s      IL_0023

// go to display
IL_0017:  br.s       IL_002b

// initialization of "variable"
IL_0019:  ldstr      "One"
IL_001e:  stloc.2
...

したがって、変数は、コンパイラによって生成されたコードによって暗黙的に操作されることはありません。

于 2012-12-15T12:33:58.233 に答える