web-dev-qa-db-ja.com

配列、ヒープ、スタック、値のタイプ

int[] myIntegers;
myIntegers = new int[100];

上記のコードでは、新しいint [100]がヒープ上の配列を生成していますか? C#を介してCLRで読んだことから、答えはイエスです。しかし、私が理解できないのは、配列内の実際のintに何が起こるかです。それらは値型であるため、たとえばボックス内に配置する必要があると思います。たとえば、myIntegersをプログラムの他の部分に渡したり、常にスタックに残っているとスタックが乱雑になります。 。それとも私は間違っていますか?私は彼らがただ箱に入れられ、アレイが存在する限りヒープ上に住むと思います。

127

配列はヒープに割り当てられ、intはボックス化されません。

混乱の原因は、参照型はヒープに割り当てられ、値型はスタックに割り当てられると人々が言っ​​ているためです。これは完全に正確な表現ではありません。

すべてのローカル変数とパラメーターはスタックに割り当てられます。これには、値型と参照型の両方が含まれます。 2つの違いは、変数にstoredが何であるかだけです。当然のことながら、値型の場合、型のvalueは変数に直接格納され、参照型の場合、型の値は格納されますヒープ上で、この値へのreferenceが変数に格納されます。

同じことがフィールドにも当てはまります。集合型(クラスまたは構造体)のインスタンスにメモリが割り当てられている場合、そのインスタンスフィールドごとにストレージを含める必要があります。参照タイプのフィールドの場合、このストレージは値への参照のみを保持し、値自体は後でヒープに割り当てられます。値型フィールドの場合、このストレージには実際の値が保持されます。

したがって、次のタイプが与えられます:

class RefType{
    public int    I;
    public string S;
    public long   L;
}

struct ValType{
    public int    I;
    public string S;
    public long   L;
}

これらの各タイプの値には16バイトのメモリが必要です(32ビットのワードサイズを想定)。フィールドIはそれぞれの値を格納するのに4バイト、フィールドSは参照を格納するのに4バイト、フィールドLはその格納に8バイトを使用します値。したがって、RefTypeValTypeの両方の値のメモリは次のようになります。

 0┌───────────────────┐
│I│
 4├───────── ───────────┤
│S│
 8├───────────────────┤
│L│
││
 16└───────────────────┘

関数に3つのローカル変数があり、RefTypeValType、およびint[]、 このような:

RefType refType;
ValType valType;
int[]   intArray;

スタックは次のようになります。

 0┌───────────────────┐
│refType│
 4├───────── ───────────┤
│valType│
││
││
││
 20├── ─────────────────┤
│intArray│
 24└───────────────── ──┘

これらのローカル変数に値を割り当てた場合、次のようになります。

refType = new RefType();
refType.I = 100;
refType.S = "refType.S";
refType.L = 0x0123456789ABCDEF;

valType = new ValType();
valType.I = 200;
valType.S = "valType.S";
valType.L = 0x0011223344556677;

intArray = new int[4];
intArray[0] = 300;
intArray[1] = 301;
intArray[2] = 302;
intArray[3] = 303;

その場合、スタックは次のようになります。

 0┌───────────────────┐
│0x4A963B68│-`refType` 
のヒープアドレス4├ ───────────────────┤
│200│-`valType.I` 
の値│0x4A984C10│-のヒープアドレス`valType.S` 
│0x44556677│-` valType.L` 
の下位32ビット│0x00112233│-`valType.L` 
の上位32ビット20├───────────────────┤
│0x4AA4C288│-「intArray」のヒープアドレス
 24└───── ──────────────┘

アドレス0x4A963B68のメモリ(refTypeの値)は次のようになります。

 0┌───────────────────┐
│100│-`refType.I` 
の値4 ├───────────────────┤
│0x4A984D88│-「refType.S」のヒープアドレス
 8├──── ───────────────┤
│0x89ABCDEF│-`refType.L` 
の下位32ビット│0x01234567│-上位32ビットof `refType.L` 
 16└────────────────────┘

アドレス0x4AA4C288のメモリ(intArrayの値)は次のようになります。

 0┌───────────────────┐
│4│-配列の長さ
 4├─── ────────────────┤
│300│-`intArray [0]` 
 8├─────────── ─────────┤
│301│-`intArray [1]` 
 12├────────────────── ──┤
│302│-`intArray [2]` 
 16├────────────────────┤
│303│-`intArray [3]` 
 20└───────────────────┘
 20

intArrayを別の関数に渡した場合、スタックにプッシュされる値は0x4AA4C288、配列のアドレス、not配列のコピーになります。

271
P Daddy

はい、アレイはヒープ上に配置されます。

配列内の整数はボックス化されません。値型がヒープに存在するからといって、必ずしもボックス化されることを意味するわけではありません。ボクシングは、intなどの値型がオブジェクト型の参照に割り当てられている場合にのみ発生します。

例えば

ボックス化しない:

int i = 42;
myIntegers[0] = 42;

ボックス:

object i = 42;
object[] arr = new object[10];  // no boxing here 
arr[0] = 42;

また、このテーマに関するエリックの投稿もご覧ください。

23
JaredPar

何が起こっているのかを理解するために、いくつかの事実があります。

  • オブジェクトは常にヒープに割り当てられます。
  • ヒープにはオブジェクトのみが含まれます。
  • 値型は、スタックに割り当てられるか、ヒープ上のオブジェクトの一部に割り当てられます。
  • 配列はオブジェクトです。
  • 配列には値の型のみを含めることができます。
  • オブジェクト参照は値型です。

したがって、整数の配列がある場合、その配列はヒープに割り当てられ、そこに含まれる整数はヒープ上の配列オブジェクトの一部になります。整数は、別個のオブジェクトとしてではなく、ヒープ上の配列オブジェクト内にあるため、ボックス化されません。

文字列の配列がある場合、それは実際には文字列参照の配列です。参照は値型なので、ヒープ上の配列オブジェクトの一部になります。文字列オブジェクトを配列に配置すると、実際には文字列オブジェクトへの参照が配列に配置され、文字列はヒープ上の別個のオブジェクトになります。

20
Guffa

あなたの質問の核心は、参照型と値型についての誤解にあると思います。これはおそらくすべての.NETおよびJava開発者が苦労したものです。

配列は単なる値のリストです。参照型の配列の場合(たとえばstring[])参照は参照型のvalueであるため、配列はヒープ上のさまざまなstringオブジェクトへの参照のリストです。内部的には、これらの参照はメモリ内のアドレスへのポインターとして実装されます。これを視覚化する場合、このような配列はメモリ上(ヒープ上)で次のようになります。

[ 00000000, 00000000, 00000000, F8AB56AA ]

これは、ヒープ上のstringオブジェクトへの4つの参照を含むstringの配列です(ここの数字は16進数です)。現在、最後のstringのみが実際に何かを指します(割り当てられるとメモリはすべてゼロに初期化されます)。この配列は基本的にC#のこのコードの結果になります。

string[] strings = new string[4];
strings[3] = "something"; // the string was allocated at 0xF8AB56AA by the CLR

上記の配列は、32ビットプログラムになります。 64ビットプログラムでは、参照のサイズは2倍になります(F8AB56AA だろう 00000000F8AB56AA)。

値型の配列がある場合(たとえばint[])次に、配列は整数のリストです。値型valuevalueは値自体です(そのため名前です)。このような配列の視覚化は次のようになります。

[ 00000000, 45FF32BB, 00000000, 00000000 ]

これは4つの整数の配列であり、2番目のintのみに値が割り当てられ(その16進数の10進数表現である1174352571に)、残りの整数は0になります(前述のように、メモリは0に初期化されます) 16進数の00000000は10進数の0です)。この配列を生成したコードは次のとおりです。

 int[] integers = new int[4];
 integers[1] = 1174352571; // integers[1] = 0x45FF32BB would be valid too

この int[]配列もヒープに格納されます。

別の例として、short[4]配列は次のようになります。

[ 0000, 0000, 0000, 0000 ]

shortvalueは2バイトの数値です。

値タイプが格納される場所は、Eric Lippertが非常によく説明しているように、実装の詳細にすぎません here 、値と参照タイプの違いに固有ではありません(動作の違い).

メソッド(参照型または値型)に何かを渡すと、その型のvaluecopyが実際にメソッドに渡されます。参照型の場合、valueは参照です(これは実装の詳細ですが、これはメモリへのポインタと考えてください)。値の型、値は物そのものです。

// Calling this method creates a copy of the *reference* to the string
// and a copy of the int itself, so copies of the *values*
void SomeMethod(string s, int i){}

ボクシングは、値型を参照型にconvertした場合にのみ発生します。このコードボックス:

object o = 5;
9
JulianR

これらは@P Daddyによる上記の答えを描いたイラストです

enter image description here

enter image description here

そして、私は私のスタイルで対応する内容を示しました。

enter image description here

2
YoungMin Park

サンプルコードにはボクシングはありません。

値型は、配列のintの場合と同様に、ヒープ上に存在できます。配列はヒープに割り当てられ、intを格納します。これは値型です。配列の内容はdefault(int)に初期化されますが、たまたまゼロになっています。

値型を含むクラスを考えます:


    class HasAnInt
    {
        int i;
    }

    HasAnInt h = new HasAnInt();

変数hは、ヒープ上にあるHasAnIntのインスタンスを参照します。たまたま値型が含まれているだけです。それは完全に大丈夫です、「i」はクラスに含まれているため、たまたまヒープ上に住んでいます。この例にもボクシングはありません。

1
Curt Nichols

十分に言われていますが、誰かがヒープ、スタック、ローカル変数、静的変数に関する明確な(ただし非公式の)サンプルとドキュメントを探している場合は、完全なJon Skeetの記事 Memory in。 NET-どこに行くか

抜粋:

  1. 各ローカル変数(メソッドで宣言されたもの)はスタックに格納されます。これには参照型変数が含まれます-変数自体はスタック上にありますが、参照型変数の値は参照(またはnull)であり、オブジェクト自体ではないことに注意してください。メソッドパラメーターもローカル変数としてカウントされますが、ref修飾子で宣言されている場合、独自のスロットを取得せず、呼び出しコードで使用される変数とスロットを共有します。詳細については、パラメーターの受け渡しに関する私の記事を参照してください。

  2. 参照型のインスタンス変数は常にヒープ上にあります。それは、オブジェクト自体が「生きる」場所です。

  3. 値型のインスタンス変数は、値型を宣言する変数と同じコンテキストに保存されます。インスタンスのメモリスロットには、インスタンス内の各フィールドのスロットが事実上含まれています。つまり、メソッド内で宣言された構造体変数は常にスタック上にあり、クラスのインスタンスフィールドである構造体変数はヒープ上にあることを意味します(前の2つの点を考えると)。

  4. すべての静的変数は、参照型または値型のどちらで宣言されているかに関係なく、ヒープに格納されます。作成されるインスタンスの数に関係なく、合計で1つのスロットのみがあります。 (ただし、その1つのスロットが存在するためにインスタンスを作成する必要はありません。)変数が存在する正確なヒープの詳細は複雑ですが、MSDNの記事で詳細に説明されています。

1
gmaran23

整数の配列がヒープに割り当てられ、それ以上でもそれ以下でもありません。 myIntegersは、intが割り当てられているセクションの先頭を参照します。その参照はスタック上にあります。

オブジェクト型のような参照型オブジェクトの配列がある場合、スタックにあるmyObjects []は、オブジェクト自体を参照する一連の値を参照します。

まとめると、一部の関数にmyIntegersを渡す場合、整数の実際の束が割り当てられている場所への参照のみを渡します。

1
Dykam