.Netブックがスタックとヒープメモリの割り当てについて説明しているのはなぜですか?


36

すべての.netブックは、値型と参照型について述べており、各型が格納されている状態(ヒープまたはスタック)を(多くの場合は誤って)示すようにしています。通常、最初の数章にあり、非常に重要な事実として提示されます。認定試験でもカバーされていると思います。スタックとヒープが(初心者).Net開発者にとっても重要なのはなぜですか?ものを割り当てると、それは機能します。


11
一部の著者は、初心者に教えることが重要であり、無関係なノイズが何であるかについて、本当に悪い判断をしています。最近見た本の中で、アクセス修飾子の最初の言及には、保護されたinternalがすでに含まていました。これは6年間のC#で使用したことがありません
...-Timwi

1
私の推測では、その部分の元の.Netドキュメントを書いた人はだれでも大したことであり、そのドキュメントは著者が元々本を基にしたものであり、それはそのままでした。
グレッグ

値型はすべてをコピーし、参照はコピーしないと言うと、参照が使用される理由が理解しやすくなり、参照が使用される理由を把握しやすくなります。
トリニダード

カーゴカルトインタビュー?
デン

回答:


37

私は、この情報が重要だと考えられる主な理由は伝統だと確信しています。 管理されていない環境では、スタックとヒープの区別が重要であり、使用するメモリを手動で割り当てて削除する必要があります。現在、ガベージコレクションが管理を行っているため、そのビットは無視されます。どのタイプのメモリが使用されているかを気にする必要がないというメッセージが本当に伝わったとは思いません。

Fedeが指摘したように、Eric Lippertはこれについて非常に興味深いことを言っています:http : //blogs.msdn.com/b/ericlippert/archive/2010/09/30/the-truth-about-value-types.aspx

その情報に照らして、私の最初の段落を調整して、基本的に読むことができます:「人々がこの情報を含めて重要だと考える理由は、過去にこの知識を必要とすることと組み合わされた不正確または不完全な情報のためです」

パフォーマンス上の理由でそれがまだ重要だと思う人のために:物事を測定し、それが重要であることがわかった場合、ヒープからスタックに何かを移動するためにどのようなアクションを取りますか?おそらく、問題領域のパフォーマンスを改善するまったく異なる方法を見つけるでしょう。


6
一部のフレームワーク実装(具体的にはXboxでコンパクト)では、レンダリング期間中(ゲーム自体)に構造体を使用して、ガベージコレクションを削減した方がよいと聞きました。他の場所では通常のタイプを使用しますが、ゲーム中にGCが実行されないように事前に割り当てられます。これは、.NETで知っているスタックとヒープに関する唯一の最適化であり、コンパクトフレームワークとリアルタイムプログラムのニーズに非常に固有のものです。
CodexArcanum

5
私はほとんど伝統の議論に同意します。ある時点で多くの経験豊富なプログラマーが、正しくて効率的なコードが必要な場合にこのような問題が発生する低レベル言語でプログラミングしたことがあるかもしれません。ただし、C ++を例にとると、アンマネージ言語です。公式の仕様では、実際に自動変数をスタックに配置する必要があるとは言われていません。C++の標準はスタックとヒープを実装の詳細として扱います。+1
stakx

36

すべての.NETブックは値型と参照型について述べており、各型が格納されている状態(ヒープまたはスタック)を(多くの場合は誤って)示すようにしています。通常、最初の数章にあり、非常に重要な事実として提示されます。

私は完全に同意します。私はいつもこれを見ています。

.NETブックでスタックとヒープメモリの割り当てについて説明しているのはなぜですか?

その理由の1つは、多くの人がCまたはC ++のバックグラウンドからC#(または他の.NET言語)に来たためです。これらの言語はストレージの有効期間に関する規則を強制するものではないため、それらの規則を理解し、それらに従うためにプログラムを慎重に実装する必要があります。

さて、これらのルールを知っており、Cでそれらに従うことは、「ヒープ」と「スタック」を理解することを必要としません。しかし、データ構造がどのように機能するかを理解していれば、多くの場合、ルールを理解して従う方が簡単です。

初心者向けの本を書くとき、著者が概念学んだのと同じ順序で説明するのは自然です。それは必ずしもユーザーにとって意味のある順序ではありません。私は最近、Scott DormanのC#4初心者向け書籍のテクニカルエディターでした。私が気に入った点の1つは、Scottがメモリ管理の実際の非常に高度なトピックから始めるのではなく、トピックのかなり賢明な順序を選択したことです。

理由の別の部分は、MSDNドキュメントの一部のページがストレージの考慮事項を強く強調していることです。特に古いMSDNドキュメントは、まだ初期の頃から残っています。そのドキュメントの多くには、これまでに削除されたことのない微妙なエラーがあり、履歴の特定の時間に特定の対象者向けに作成されたことを覚えておく必要があります。

スタックとヒープが(初心者).NET開発者にとって重要なのはなぜですか?

私の意見では、そうではありません。理解する必要があるのは、次のようなものです。

  • 参照型と値型のコピーセマンティクスの違いは何ですか?
  • 「ref int x」パラメーターはどのように動作しますか?
  • なぜ値型は不変でなければならないのですか?

等々。

ものを割り当てると、それは機能します。

それが理想です。

今、それが重要な状況があります。ガベージコレクションはすばらしく、比較的安価ですが、無料ではありません。小さな構造をコピーすることは比較的安価ですが、無料ではありません。収集圧力のコストと過剰なコピーのコストのバランスを取る必要がある現実的なパフォーマンスシナリオがあります。これらの場合、関連するすべてのメモリのサイズ、場所、および実際の有効期間を十分に理解しておくと非常に役立ちます。

同様に、スタック上にあるものとヒープ上にあるもの、およびガベージコレクターが移動できるものを知る必要がある現実的な相互運用シナリオがあります。そのため、C#には「fixed」、「stackalloc」などの機能があります。

しかし、これらはすべて高度なシナリオです。理想的には、初心者プログラマーはこのようなことを心配する必要はありません。


2
エリックに答えてくれてありがとう。このテーマに関する最近のブログ投稿は、実際に質問を投稿するきっかけとなりました。
グレッグ

13

皆さんはポイントを逃しています。スタック/ヒープの区別が重要である理由は、スコープのためです。

struct S { ... }

void f() {
    var x = new S();
    ...
 }

いったんxがスコープ外になる、作成されたオブジェクトは断固れてしまっ。それは、ヒープではなくスタックに割り当てられているためです。メソッドの「...」部分には、その事実を変更できるものは何もありませんでした。特に、割り当てまたはメソッド呼び出しは、S構造体のコピーを作成するだけで、生き続けるための新しい参照を作成することはできませんでした。

class C { ... }

void f() {
     var x = new C();
     ...
}

まったく違うストーリー!xはヒープ上にあるため、そのオブジェクト(つまり、オブジェクトのコピーではなく、オブジェクト自体)は、xがスコープ外になった後も存続し続けることができます。実際、それ存続しない唯一の方法は、xが唯一の参照である場合です。「...」部分の割り当てまたはメソッド呼び出しが、xがスコープから外れるまでに「生きている」他の参照を作成した場合、そのオブジェクトは生き続けます。

これは非常に重要な概念であり、「何となぜ」を本当に理解する唯一の方法は、スタックとヒープの割り当ての違いを知ることです。


スタック/ヒープの議論とともに本で前に提示されたその議論を見たことがあるかどうかはわかりませんが、それは良いものです。+1
グレッグ

2
C#は、クロージャを生成する方法のため、でコード...引き起こす可能性がxコンパイラ生成されたクラスのフィールドに変換されるので、指示された範囲アウトラスト。個人的には、暗黙のホイストの考えに嫌気がさしますが、言語デザイナーはそれを好むようです(ホイストされる変数に宣言で何かを指定して要求するのではなく)。プログラムの正確性を確保するために、オブジェクトに存在する可能性のあるすべての参照を考慮する必要がしばしばあります。ルーチンが戻るまでに、渡された参照のコピーが残っていないことを知っておくと便利です。
-supercat

1
「構造体がスタック上にある」のように、適切な文では、保管場所は次のように宣言されている場合ということでstructType foo、保管場所は、fooそのフィールドの内容を保持しています。fooがスタック上にある場合、そのフィールドも同様です。fooがヒープ上にある場合、そのフィールドも同様です。場合はfoo、ネットワークのApple IIであるので、そのフィールドです。対照的に、fooクラス型である場合、オブジェクトnullまたはオブジェクトへの参照を保持します。クラスタイプfooがオブジェクトのフィールドを保持していると言える唯一の状況は、それがクラスの唯一のフィールドであり、それ自体への参照を保持している場合です。
-supercat

+1、私はここであなたの洞察を愛し、それが有効だと思います...しかし、私はそれが本がそのような深さでこのトピックをカバーする理由についての正当性を感じません。ここで説明した内容は、この本の3つまたは4つの章に取って代わり、より役立つものになりそうです。
フランクV

1
私が知っていることから、構造体は必要ありませんし、実際に常にスタックに行くこともありません。
サラ

5

なぜ彼らがこのトピックを扱っているのかについて、@ Kirkはそれが重要な概念であり、あなたが理解しなければならないことに同意します。メカニズムを熟知すればするほど、スムーズに実行される優れたアプリケーションを作成するために、より良いことができます。

現在、Eric Lippertは、ほとんどの著者がこのトピックを正しく扱っていないことに同意しているようです。彼のブログを読んで、内部にあるものを深く理解することをお勧めします。


2
Ericの投稿は、値と参照型の明白な特性だけを知っておく必要があるということを指摘しており、実装が同じままであることさえ期待すべきではありません。私は、スタックなしでC#を再実装する効率的な方法があることを提案するのはかなり疑問に思うと思いますが、彼のポイントは正しいです:言語仕様の一部ではありません。したがって、私が考えることができるこの説明を使用する唯一の理由は、他の言語、特にCを知っているプログラマに役立つall話であるということです。彼らがそのthey話を知っている限り、多くの文献は明らかにしません。
ジェレミー

5

まあ、私はそれが管理された環境の全体のポイントだと思いました。私はこれを基礎となるランタイムの実装の詳細と呼んでさえいれば、それはいつでも変更される可能性があるので、想定してはいけません。

.NETについてはあまり知りませんが、私が知る限り、実行前にJITされました。たとえば、JITはエスケープ分析を実行できますが、突然、スタック上または一部のレジスターにオブジェクトが存在することになります。これを知ることはできません。

著者がそれを非常に重要だと考えているため、または読者がそうすることを想定しているために、いくつかの本がそれをカバーしていると思います(たとえば、「C ++プログラマーのためのC#」

それにもかかわらず、私は「メモリが管理されている」以上に言うことは多くないと思います。さもなければ、人々は間違った結論を出すかもしれません。


2

明示的に管理する必要がない場合でも、メモリ割り当てを効率的に使用するには、メモリ割り当ての仕組みを理解する必要があります。これは、コンピューターサイエンスのほとんどすべての抽象化に当てはまります。


2
マネージ言語では、値型と参照型の違いを知る必要がありますが、それを超えて、ボンネットの下でどのように管理されているかを考えて車軸に簡単に巻き付くことができます。例えばここを参照してください:stackoverflow.com/questions/4083981/...
ロバート・ハーヴェイ

ロバート

割り当てられたヒープと割り当てられたスタックの違いは、値型と参照型の違いを説明するものです。
ジェレミー

3
@ジェレミー:そうでもない。参照してくださいblogs.msdn.com/b/ericlippert/archive/2010/09/30/...
ロバート・ハーヴェイ

1
Jeremy、ヒープとスタック割り当ての違いは、値型と参照型の両方がヒープ上にある場合がありますが、動作が異なるため、値型と参照型の異なる動作を説明できません。理解するより重要なことは、(たとえば)参照型と値型の参照渡しを使用する必要がある場合です。これは、「ヒープ上にあるのではなく」「値型または参照型である」だけに依存します。
ティムグッドマン

2

それが違いを生むことができるいくつかのエッジケースがあります。デフォルトのスタックスペースは1メガバイトで、ヒープは数ギガです。したがって、ソリューションが多数のオブジェクトを保持している場合は、十分なヒープスペースを確保しながらスタックスペースを使い果たすことができます。

ただし、ほとんどの場合、かなりアカデミックです。


はい、しかし、これらの本のいずれも参照自体がスタックに保存されていることを説明するのに苦労するのではないかと思います。したがって、スタックオーバーフローが発生する可能性のある参照型または値型がたくさんあるかどうかは関係ありません。
ジェレミー

0

あなたが言うように、C#はメモリ管理を抽象化することになっています。ヒープとスタックの割り当ては、理論的には開発者が知る必要のない実装の詳細です。

問題は、これらの実装の詳細を参照せずに直感的な方法で説明するのが非常に難しいことです。可変の値の型を変更するときの観察可能な動作を説明してください-スタック/ヒープの区別を参照せずに行うことはほとんど不可能です。または、そもそもなぜ言語に値型があるのか​​、そしていつそれらを使用するのかを説明してみませんか?言語の意味を理解するには、区別を理解する必要があります。

PythonやJavaScriptについての本は、言及すれば大したことではないことに注意してください。これは、すべてがヒープに割り当てられているか、不変であるためです。つまり、異なるコピーのセマンティクスは決して機能しません。これらの言語では、メモリの抽象化が機能しますが、C#ではリークが発生します。

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