メモリ割り当て:スタックとヒープ?


83

スタックとヒープの間のメモリ割り当ての基本と混同しています。標準の定義(誰もが言うこと)に従って、すべての値型スタックに割り当てられ、参照型はヒープに入れられます。

次の例を考えてみましょう。

class MyClass
{
    int myInt = 0;    
    string myString = "Something";
}

class Program
{
    static void Main(string[] args)
    {
       MyClass m = new MyClass();
    }
}

さて、メモリ割り当てはc#でどのように発生しますか?MyClass(つまりm)のオブジェクトは完全にヒープに割り当てられますか?つまり、int myIntstring myStringの両方がヒープに行くのだろうか?

または、オブジェクトは2つの部分に分割され、スタックとヒープの両方のメモリ位置に割り当てられますか?


私は、これらの長年の愚かな発言が間違っていたとしても、いくつかの良い答えがあると信じているという理由だけで賛成票を投じました。ただし、提案された「二重割り当て」に対する些細な反論を思い付くのは非常に簡単です(ヒント:クラスオブジェクトは関数呼び出しの境界を越えて生きることができます-そして多くの場合そうします)。

これはあなたの質問に答えますか?スタックとヒープはどこにありますか?
Olivier Rogier

回答:


55

mはヒープに割り当てられ、これにはが含まれmyIntます。プリミティブ型(および構造体)がスタックに割り当てられる状況は、メソッドの呼び出し中に発生します。メソッドの呼び出しでは、スタック上のローカル変数用のスペースが割り当てられます(高速であるため)。例えば:

class MyClass
{
    int myInt = 0;

    string myString = "Something";

    void Foo(int x, int y) {
       int rv = x + y + myInt;
       myInt = 2^rv;
    }
}

rvxyすべてのスタックになります。myIntヒープ上のどこかにあります(そしてthisポインタを介してアクセスする必要があります)。


7
重要な補遺は、「スタック」と「ヒープ」が実際には.NETでの実装の詳細であることを覚えておくことです。スタックベースの割り当てをまったく使用しないC#の合法的な実装を作成することは完全に可能です。
JSBձոգչ2010

5
私はそれらがそのように扱われるべきであることに同意します、しかしそれらが純粋に実装の詳細であるということは完全に真実ではありません。これは、公開APIドキュメントおよび言語標準(EMCA-334、ISO / IEC 23270:2006)に明示的に記載されています(つまり、「構造体の値は「スタックに格納されます」。注意深いプログラマーは、構造体を適切に使用することでパフォーマンスを向上させることができます。 ")しかし、ええ、ヒープ割り当ての速度がアプリケーションのボトルネックである場合は、おそらくそれを間違って行っています(または間違った言語を使用しています)。
泥2010

65

実装の詳細として、オブジェクトがどこに割り当てられるかという問題を検討する必要があります。オブジェクトのビットがどこに格納されているかは、正確には関係ありません。オブジェクトが参照型であるか値型であるかは重要かもしれませんが、ガベージコレクションの動作を最適化する必要が生じるまで、オブジェクトがどこに格納されるかを心配する必要はありません。

現在の実装では、参照型は常にヒープに割り当てられますが、値型スタックに割り当てられる場合がありますが、必ずしもそうとは限りません。値型は、参照型に含まれておらず、レジスタに割り当てられていない、ボックス化されていないエスケープされていないローカル変数または一時変数である場合にのみ、スタックに割り当てられます。

  • 値型が(あなたの例のように)クラスの一部である場合、それは最終的にヒープになります。
  • ボックス化されている場合は、ヒープ上に配置されます。
  • 配列内にある場合は、ヒープ上に配置されます。
  • 静的変数の場合は、ヒープ上に配置されます。
  • クロージャによってキャプチャされた場合、ヒープ上に配置されます。
  • イテレータまたは非同期ブロックで使用されている場合は、ヒープ上に配置されます。
  • 安全でないコードまたは管理されていないコードによって作成された場合は、任意のタイプのデータ構造(必ずしもスタックまたはヒープである必要はありません)に割り当てることができます。

見逃したことはありますか?

もちろん、このトピックに関するEric Lippertの投稿にリンクしなかった場合、私は失望します。


1
エド:それはいつ重要なのですか?
Gabe 2010

1
@Gabe:ビットがどこに格納されているかは重要です。たとえば、クラッシュダンプをデバッグしている場合、オブジェクト/データを探す場所がわからない限り、それほど遠くまで行くことはありません。
ブライアンラスムッセン2010

14
見逃した状況は次のとおりです。値型が安全でないポインタを介してアクセスされたアンマネージコードからのものである場合、スタックにもマネージヒープにも存在しない可能性があります。管理されていないヒープ上にあるか、ヒープでさえないデータ構造内にある可能性があります。「ヒープ」があるという考え全体も神話です。数十のヒープが存在する可能性があります。また、ジッタが値を登録することを選択した場合、その値はスタックまたはヒープになく、レジスタにあります。
Eric Lippert 2010

1
エリック・リペットのパート2は素晴らしい読み物でした、リンクをありがとう!
ダンベチャード2013年

1
これは、インタビューでは尋ねられますが、実際には尋ねられないため、重要です。:)
マヤンク2015

22

「すべてのVALUEタイプがスタックに割り当てられます」は非常に間違っています。構造体変数、メソッド変数としてスタック上に存在できます。ただし、タイプのフィールドはそのタイプと同じです。フィールドの宣言型がクラスの場合、値はそのオブジェクトの一部としてヒープ上にあります。フィールドの宣言型が構造体である場合、その構造体が存在する場所であればどこでも、フィールドはその構造体の一部です。

メソッド変数でさえ、キャプチャされている場合(ラムダ/アノンメソッド)、またはイテレータブロックの一部(たとえば)、ヒープ上にある可能性があります。


1
また、ボクシングを忘れないでください。object x = 12;メソッドにある場合、12は整数(値型)であってもヒープに格納されます。
Gabe 2010

@Gabe:値型の格納場所は、値型のフィールド(パブリックおよびプライベート)を内部に保持します。参照型の格納場所は、保持するnullか、適切な型のヒープオブジェクトへの参照を保持します。すべての値型には、対応するヒープオブジェクト型があります。値型を参照型の格納場所に格納しようとすると、対応するヒープオブジェクト型の新しいオブジェクトが生成され、すべてのフィールドがその新しいオブジェクトにコピーされ、オブジェクトへの参照が参照型の格納場所に格納されます。C#は、値型とオブジェクト型が同じであると
偽り

...そのような視点は理解よりも混乱を追加します。List<T>.Enumeratorその型の変数に格納されているボックス化されていないものは、値型であるため、値のセマンティクスを示します。ただし、List<T>.Enumerator型の変数に格納されているAはIEnumerator<T>、参照型のように動作します。後者を前者とは異なるタイプと見なすと、動作の違いは簡単に説明できます。それらが同じタイプであるふりをすると、それらについて推論するのがはるかに難しくなります。
スーパーキャット2012

12

2

スタック

stack格納するメモリのブロックであるlocal variablesparameters。関数が開始および終了すると、スタックは論理的に拡大および縮小します。

次の方法を検討してください。

public static int Factorial (int x)
{
    if (x == 0) 
    {
        return 1;
    }

    return x * Factorial (x - 1);
}

このメソッドは再帰的です。つまり、それ自体を呼び出します。メソッドが入力されるたびに、新しいintがスタック割り当てられ、メソッドが終了するたびに、intの割り当てが解除されます


ヒープ

  • ヒープは、objects(つまりreference-type instances)が存在するメモリのブロックです。新しいオブジェクトが作成されるたびに、そのオブジェクトはヒープに割り当てられ、そのオブジェクトへの参照が返されます。プログラムの実行中、新しいオブジェクトが作成されると、ヒープがいっぱいになり始めます。ランタイムには、ヒープからオブジェクトの割り当てを定期的に解除するガベージコレクターがあるため、プログラムは実行されませんOut Of Memory。オブジェクトは、それ自体が参照されていない場合はすぐに割り当て解除の対象になりますalive
  • ヒープも格納しstatic fieldsます。ヒープに割り当てられたオブジェクト(ガベージコレクションが発生する可能性がある)とは異なり、these live until the application domain is torn down

次の方法を検討してください。

using System;
using System.Text;

class Test
{
    public static void Main()
    {
        StringBuilder ref1 = new StringBuilder ("object1");
        Console.WriteLine (ref1);
        // The StringBuilder referenced by ref1 is now eligible for GC.

        StringBuilder ref2 = new StringBuilder ("object2");
        StringBuilder ref3 = ref2;
        // The StringBuilder referenced by ref2 is NOT yet eligible for GC.
        Console.WriteLine (ref3); // object2
    }
}    

上記の例では、変数ref1によって参照されるStringBuilderオブジェクトを作成することから始め、次にその内容を書き出します。そのStringBuilderオブジェクトは、その後何も使用しないため、すぐにガベージコレクションの対象になります。次に、変数ref2によって参照される別のStringBuilderを作成し、その参照をref3にコピーします。それ以降はref2は使用されませんが、ref3は同じStringBuilderオブジェクトを存続させ、ref3の使用が終了するまでコレクションの対象にならないようにします。

値型インスタンス(およびオブジェクト参照)は、変数が宣言されている場所ならどこにでも存在します。インスタンスがクラスタイプ内のフィールドとして、または配列要素として宣言されている場合、そのインスタンスはヒープ上に存在します。


1

簡単な対策

値型はスタックに適用できます。これは、未来主義者のデータ構造に割り当てることができる実装上の詳細です。

したがって、値と参照型がどのように機能するかを理解することをお勧めします。値型は値によってコピーされます。つまり、値型をパラメータとしてFUNCTIONに渡すと、本来コピーされるのではなく、まったく新しいコピーが作成されます。 。

参照型は参照によって渡されます(参照が将来のバージョンでアドレスを再度格納することを考慮しないでください。他のデータ構造に格納される可能性があります)。

だからあなたの場合

myIntは、参照型をオフコースするクラスにカプセル化されるintであるため、「THEHEAP」に格納されるクラスのインスタンスに関連付けられます。

私は提案します、あなたはERICLIPPERTSによって書かれたブログを読み始めることができます。

エリックのブログ


1

オブジェクトが作成されるたびに、ヒープと呼ばれるメモリ領域に入ります。intやdoubleなどのプリミティブ変数は、ローカルメソッド変数の場合はスタックに割り当てられ、メンバー変数の場合はヒープに割り当てられます。メソッドでは、メソッドが呼び出されるとローカル変数がスタックにプッシュされ、メソッド呼び出しが完了するとスタックポインタがデクリメントされます。マルチスレッドアプリケーションでは、各スレッドに独自のスタックがありますが、同じヒープを共有します。これが、ヒープスペースでの同時アクセスの問題を回避するためにコードに注意を払う必要がある理由です。スタックはスレッドセーフです(各スレッドには独自のスタックがあります)が、コードによる同期で保護されていない限り、ヒープはスレッドセーフではありません。

このリンクも役立ちますhttp://www.programmerinterview.com/index.php/data-structures/difference-between-stack-and-heap/


0

mはMyClassのオブジェクトへの参照であるため、mはメインスレッドのスタックに格納されますが、MyClassのオブジェクトはヒープに格納されます。したがって、myIntとmyStringはヒープに格納されます。mは単なる参照(メモリへのアドレス)であり、メインスタック上にあることに注意してください。mが割り当て解除されたら、GCはヒープからMyClassオブジェクトをクリアします。詳細については、この記事の4つの部分すべてをお読み くださいhttps://www.c-sharpcorner.com/article/C-Sharp-heaping-vs-stacking-in-net-パートi /

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.