メモリ管理されていないプログラミングの複雑さは何ですか?


24

または、言い換えると、自動化されたガベージコレクションはどのような特定の問題を解決しましたか?低レベルのプログラミングを行ったことがないので、リソースの解放がどれほど複雑になるかわかりません。

GCが対処する種類のバグは、(少なくとも外部の観察者にとっては)自分の言語、ライブラリ、概念、イディオムなどをよく知っているプログラマーがしないようなことのように見えます。しかし、私は間違っている可能性があります:手動メモリ処理は本質的に複雑ですか?


3
ガーベッジコレクションに関するウィキペディアの記事、具体的にはそのメリット
ヤニス

もう1つの利点はセキュリティです。たとえば、バッファオーバーランは非常に悪用されやすく、他の多くのセキュリティ脆弱性はメモリ(誤)管理に起因します。
StuperUser

7
@StuperUser:それはメモリの起源とは何の関係もありません。GCからのオーバーランメモリを正常にバッファできます。通常、GC言語はこれを防ぐという事実は直交的であり、GCテクノロジーと比較して30年以内に遅れている言語は、バッファーオーバーラン保護も提供します。
DeadMG

回答:


29

低レベルのプログラミングを行ったことがないので、リソースの解放がどれほど複雑になるかわかりません。

「低レベル」の定義が時間とともにどのように変化するか面白い。私が最初にプログラミングを学んでいたとき、単純な割り当て/解放パターンを可能にする標準化されたヒープモデルを提供する言語は、実際に高レベルと見なされていました。では、低レベルのプログラミング、あなたは(!割り当てますが、メモリ位置そのものではない)、メモリの自分を追跡する必要があり、またはあなたが本当に空想を感じた場合は、独自のヒープアロケータを記述します。

そうは言っても、それについて本当に怖いことや「複雑なこと」はまったくありません。あなたが子供の頃、お母さんがおもちゃで遊んだらおもちゃを片付けるように言ったのを覚えていますか?彼女はあなたのメイドではなく、あなたのために部屋を掃除しませんでしたか?メモリ管理は、これと同じ原則をコードに適用したものです。(GCはあなたきれいにするメイドを持っているようなものですが、彼女はとても怠け者であり、少し無知です。)その原理は単純です。不要になった変数のメモリを解放します。(単一所有権の原則)これには、割り当てごとに1つの呼び出しが必要であり、所有権とクリーンアップを何らかの方法で自動化するスキームがいくつか存在するため、その呼び出しを独自のコードに記述する必要さえありません。

ガベージコレクションは、2つの問題を解決することになっています。それらの1つで常に非常に悪い仕事をし、実装に応じて他の1つでうまくいくかもしれません。問題は、メモリリーク(メモリを使い終わった後に保持する)とぶら下がり参照(メモリを使い果たす前にメモリを解放する)です。両方の問題を見てみましょう。

ぶら下がり参照:これは本当に深刻なものであるため、最初にこれについて議論します。同じオブジェクトへの2つのポインターがあります。それらの1つを解放し、他の1つに気付かない。その後、後の時点で、2番目のものを読み取ろうと(または書き込みまたは解放しようと)します。結果は未定義です。気付かない場合は、簡単にメモリを破損する可能性があります。ガベージコレクションは、すべての参照がなくなるまで何も解放されないようにすることで、この問題を不可能にします。完全に管理された言語では、外部の管理されていないメモリリソースを処理するまで、これはほとんど機能します。それから、スクエア1に戻ります。そして、管理されていない言語では、物事はまだトリッキーです。(Mozillaで突く

幸いなことに、この問題への対処は基本的に解決された問題です。ガベージコレクターは必要ありません。デバッグメモリマネージャーが必要です。 たとえば、Delphiを使用し、単一の外部ライブラリと単純なコンパイラディレクティブを使用して、アロケータを「フルデバッグモード」に設定できます。これにより、使用メモリを追跡する機能を有効にする代わりに、無視できる(5%未満の)パフォーマンスオーバーヘッドが追加されます。オブジェクトを解放すると、メモリがいっぱいになります0x80バイト(デバッガで簡単に認識可能)および解放されたオブジェクトで仮想メソッド(デストラクタを含む)を呼び出そうとすると、オブジェクトが作成されたときに、3つのスタックトレースを含むエラーボックスでプログラムに気づき、割り込みます。それが解放されたとき、そして私が今いる場所に加えて、いくつかの他の有用な情報は、例外を発生させます。これは明らかにリリースビルドには適していませんが、参照先の問題を追跡して修正するのは簡単です。

2番目の問題はメモリリークです。これは、不要になったときに割り当てられたメモリを保持し続けると発生します。ガベージコレクションの有無にかかわらず、どの言語でも発生する可能性があり、コードを正しく記述することによってのみ修正できます。ガベージコレクションは、特定の形式のメモリリーク(まだ解放されていないメモリへの有効な参照がない場合に発生する種類)の軽減に役立ちます。つまり、プログラムが終了するまでメモリが割り当てられたままになります。残念ながら、自動化された方法でこれを達成する唯一の方法は、すべての割り当てをメモリリークに変えることです!

そのようなことを言おうとすると、GCの支持者にうんざりしてしまうでしょうから、説明させてください。メモリリークの定義は、不要になったときに割り当てられたメモリを保持していることに注意してください。何かへの参照がないことに加えて、解放する必要があるときにコンテナオブジェクトに保持するなど、不要な参照を行うことでメモリをリークすることもできます。これを行うとメモリリークが発生することがありますが、GCを持っているかどうかを追跡するのは非常に困難です。メモリへの完全に有効な参照が含まれ、デバッグツール用の明確な「バグ」がないためです。キャッチ。私の知る限り、このタイプのメモリリークをキャッチできる自動化ツールはありません。

したがって、ガベージコレクターは、参照なしのさまざまなメモリリークにのみ関与します。これは、自動化された方法で処理できる唯一のタイプだからです。すべての参照をすべて監視し、それを指す参照がゼロになるとすぐにすべてのオブジェクトを解放できれば、少なくとも参照なしの問題に関しては完璧です。これを自動化された方法で行うことは参照カウントと呼ばれ、いくつかの限られた状況で行うことができますが、対処する独自の問題があります。(たとえば、オブジェクトAへの参照を保持するオブジェクトBへの参照を保持するオブジェクトA。参照カウント方式では、AまたはBへの外部参照がない場合でも、オブジェクトを自動的に解放することはできません。)ガベージコレクターはトレースを使用します代わりに、既知の良好なオブジェクトのセットから開始し、参照するすべてのオブジェクトを検索し参照するすべてのオブジェクトを検索するなど、すべてが見つかるまで再帰的に実行します。トレースプロセスで検出されないものはすべてゴミであり、破棄することができます。(もちろん、これを成功させるには、トレースガベージコレクターが参照とポインターのように見えるメモリのランダムな部分との違いを常に追跡できるように、型システムに特定の制限をかけるマネージ言語が必要です。)

トレースには2つの問題があります。まず、処理速度が遅く、その間、プログラムを一時停止して競合状態を回避する必要があります。これにより、プログラムがユーザーとやり取りすることになっている場合に顕著な実行の中断、またはサーバーアプリのパフォーマンスが低下する可能性があります。これは、割り当てたメモリを「世代」に分割するなど、さまざまな手法によって軽減できます。たとえば、最初に割り当てを収集しない場合、しばらくの間は滞留する可能性があります。.NETフレームワークとJVMの両方が世代別ガベージコレクターを使用します。

残念なことに、これは2番目の問題につながります。つまり、使い終わってもメモリが解放されないということです。オブジェクトを終了した直後にトレースを実行しない限り、次のトレースまで、または最初の世代を過ぎた場合はさらに長くなります。実際、私が見た.NETガベージコレクターの最良の説明の1つは、プロセスをできるだけ速くするために、GCは可能な限りコレクションを延期する必要があることを説明しています!したがって、メモリリークの問題は、可能な限り多くのメモリを可能な限りリークすることによって、むしろ奇妙に「解決」されます。 これは、GCがすべての割り当てをメモリリークに変えるという意味です。実際には、任意のオブジェクトがします保証はありませんこれまでに収集すること。

必要なときにメモリがまだ再利用されているのに、なぜこれが問題なのですか?いくつかの理由があります。まず、かなりの量のメモリを消費する大きなオブジェクト(たとえばビットマップ)を割り当てることを想像してください。そして、使い終わったらすぐに、同じ(または同じに近い)量のメモリを使用する別の大きなオブジェクトが必要になります。最初のオブジェクトが解放された場合、2番目のオブジェクトはそのメモリを再利用できます。ただし、ガベージコレクションされたシステムでは、次のトレースが実行されるのを待っている可能性があります。そのため、2番目の大きなオブジェクトのメモリを不必要に浪費することになります。基本的には競合状態です。

第二に、特に大量のメモリを不必要に保持すると、最新のマルチタスクシステムで問題が発生する可能性があります。物理メモリを使いすぎると、プログラムまたは他のプログラムがページング(メモリの一部をディスクにスワップ)しなければならず、本当に速度が低下します。サーバーなどの特定のシステムでは、ページングは​​システムの速度を低下させるだけでなく、負荷がかかっていると全体をクラッシュさせる可能性があります。

ぶら下がり参照問題のように、参照なし問題はデバッグメモリマネージャで解決できます。繰り返しになりますが、DelphiのFastMMメモリマネージャのフルデバッグモードについて説明します。これは、最もよく知っているデバッグモードだからです。(他の言語にも同様のシステムが存在すると確信しています。)

FastMMで実行されているプログラムが終了したときに、オプションで、解放されなかったすべての割り当ての存在をレポートすることができます。フルデバッグモードではさらに一歩進んで、割り当ての種類だけでなく、割り当てられたときのスタックトレースや、リークした割り当てごとのその他のデバッグ情報を含むファイルをディスクに保存できます。これにより、参照なしのメモリリークを簡単に追跡できます。

実際に見てみると、ガベージコレクションは、ぶら下がり参照の防止に効果がある場合とそうでない場合があり、メモリリークの処理では一般的に悪い仕事をします。実際、その利点の1つは、ガベージコレクション自体ではなく、副作用です。ヒープの圧縮を実行する自動化された方法を提供します。これにより、長時間継続して実行され、メモリチャーンの程度が高いプログラムを強制終了する可能性がある難解な問題(ヒープフラグメンテーションによるメモリ枯渇)を防ぐことができ、ヒープの圧縮はガベージコレクションなしではほとんど不可能です。ただし、最近の優れたメモリアロケーターはバケットを使用して断片化を最小限に抑えています。つまり、断片化は極端な状況でのみ問題になります。ヒープの断片化が問題になる可能性が高いプログラムの場合、圧縮ガベージコレクタを使用することをお勧めします。しかし、IMOはそれ以外の場合、ガベージコレクションの使用は時期尚早な最適化であり、それが「解決する」問題に対するより良い解決策が存在します。


5
私はこの答えが大好きです。時々それを読み続けています。関連する発言を思い付くことができないので、私が言えることはすべてです-ありがとう。
vemv

3
はい、GCは(少なくともしばらく)メモリを「リーク」する傾向がありますが、メモリアロケータが収集前にメモリを割り当てることができない場合にメモリを収集するため、これは問題ではありません。GC以外の言語では、リークは常にリークのままです。つまり、収集されていないメモリが多すぎるために実際にメモリが不足する可能性があります。「ガベージコレクションは時期尚早な最適化です」... GCは最適化ではなく、それを念頭に置いて設計されたものではありません。そうでなければ、良い答え。
トーマスエディング

7
@ThomasEding:GCは確かに最適化です。パフォーマンスやその他のさまざまなプログラム品質指標を犠牲にして、最小限のプログラマの労力で最適化されます。
メイソンウィーラー14年

5
Mozillaがまったく異なる結論に達したため、ある時点でMozillaのバグトラッカーを指すのは面白いことです。Firefoxには、メモリ管理の誤りに起因する無数のセキュリティ問題があります。これは、検出されたエラーを修正するのがいかに簡単かということではないことに注意してください。通常、開発者が問題に気付くまでに損傷はすでに行われています。Mozillaは、そもそもこうしたエラーが発生するのを防ぐために、Rustプログラミング言語に正確に資金を提供しています。

1
Rustはガベージコレクションを使用しませんが、実行時にエラーを検出するためにデバッガーを使用するのではなく、広範なコンパイル時チェックを行うだけで、Masonが記述する方法とまったく同じ参照カウントを使用します...
Sean Burton

13

C ++のRAIIなどの現在の一般的なシステムで使用されているガベージコレクタと同等の時代からのガベージコレクションではないメモリ管理手法を検討します。このアプローチを考えると、自動化されたガベージコレクションを使用しない場合のコストは最小限に抑えられ、GC自体に多くの問題が生じます。そのため、「あまりない」があなたの問題の答えであることをお勧めします。

人々が非GCについて考えるとき、彼らが考えるmallocと覚えておいてくださいfree。しかし、これは巨大な論理的誤acyです。1970年代初期の非GCリソース管理を90年代後半のガベージコレクタと比較することになります。これは明らかに使用されていたとき、ガベージコレクタはむしろ不公平comparison-であるmallocfree設計されていたが、私の記憶が正しければ、意味のあるプログラムを実行するにはあまりにも遅かったです。漠然と同等の期間から何かを比較することは、例えばunique_ptr、はるかに意味があります。

ガベージコレクターは、参照サイクルをより簡単に処理できますが、これらは非常にまれな経験です。さらに、GCはすべてのメモリ管理を処理するため、GCはコードを「スロー」できます。つまり、GCは開発サイクルの高速化につながる可能性があります。

一方、彼らは自分のGCプールを除くどこから来たメモリを扱うときに大きな問題に遭遇する傾向があります。さらに、オブジェクトの所有権を考慮する必要があるため、同時実行性が関係する場合、多くの利点を失います。

編集:あなたが言及するものの多くはGCとは何の関係もありません。メモリ管理とオブジェクト指向を混同しています。参照してください、C ++のような完全に管理されていないシステムでプログラミングする場合、必要なだけ境界チェックを行うことができ、標準コンテナクラスが提供します。たとえば、境界チェックや厳密な型指定に関するGCはありません。

あなたが言及した問題は、GCではなくオブジェクト指向によって解決されます。配列メモリの起源と、外部に書き込まないようにすることは、直交する概念です。

編集:より高度な技術を使用すると、動的メモリ割り当ての形式をまったく必要としないことに注意してください。たとえば、thisの使用を検討してください。これは、動的割り当てをまったく行わずにC ++でY結合を実装します。


ここでの詳細な議論は整理されました。もし誰もがチャットに参加してトピックをさらに議論することができたら、本当に感謝しています。

@DeadMG、コンビネーターが何をすべきか知っていますか?それは結合することになっています。定義上、コンビネーターは自由変数のない関数です。
SKロジック

2
@ SK-logic:テンプレートのみで実装し、メンバー変数を持たないようにすることもできます。しかし、その場合、クロージャを渡すことができなくなり、その有用性が大幅に制限されます。チャットに参加しますか?
-DeadMG

@DeadMG、定義は非常に明確です。自由変数はありません。Yコンビネータを定義することが可能であれば(適切に、あなたのやり方ではなく)、どの言語でも「十分に機能する」と考えます。大きな「+」は、S、K、およびIコンビネータを介して定義できる場合です。それ以外の場合、言語は十分に表現力がありません。
SKロジック

4
@ SK-logic:親切なモデレーターが尋ねたように、チャット来てみませんか?また、YコンビネータはYコンビネータであり、ジョブを実行しますが、実行しません。HaskellバージョンのYコンビネーターは基本的にこれとまったく同じで、表現された状態があなたから隠されているだけです。
DeadMG

11

ガベージコレクション言語が提供すると思われる「リソースの解放を心配する必要がない」というのは、かなりの程度の幻想です。マップを削除せずにマップに追加し続けると、私が話していることをすぐに理解できます。

実際、GCed言語で書かれたプログラムでは、メモリリークが非常に頻繁に発生します。これらの言語はプログラマを怠laにし、言語が常に何らかの方法で(魔法のように)処理する誤ったセキュリティ感覚を獲得する傾向があるためですもう考える必要はありません。

ガベージコレクションは、別のより高貴な目標を持つ言語に必要な機能にすぎません。すべてをオブジェクトへのポインターとして扱い、同時にプログラマーがポインターであることをプログラマーから隠して、プログラマーがコミットできないようにすることです。ポインター演算などを試みることによる自殺。すべてがオブジェクトであるということは、GCed言語は非GCed言語よりもはるかに頻繁にオブジェクトを割り当てる必要があることを意味します。つまり、プログラマがそれらのオブジェクトの割り当てを解除する負担をかけると、非常に魅力的ではなくなります。

また、ガベージコレクションは、すべての割り当てを解除するために式を個別のステートメントに分解することなく、プログラマーに関数型プログラミングの方法で式内のオブジェクトを操作するタイトなコードを書く能力を提供するのに役立ちます式に参加する単一のオブジェクト。

それ以外にも、私の回答の冒頭で「かなりの程度まで幻想だ」書いていることに注意してください。幻想だとは書いていません。私はそれがほとんど幻想であることさえ書きませんでした。ガベージコレクションは、オブジェクトの割り当てを解除するという面倒なタスクをプログラマから取り除くのに役立ちます。したがって、この意味では生産性機能です。


4

ガベージコレクターは「バグ」に対処しません。これは、一部の高レベル言語のセマンティクスに必要な部分です。GCでは、レキシカルクロージャなどの高レベルの抽象化を定義できますが、手動のメモリ管理では、これらの抽象化はリークしやすく、リソース管理の低レベルに不必要にバインドされます。

コメントで言及されている「単一所有権の原則」は、このような漏れやすい抽象化の非常に良い例です。開発者は、特定の基本データ構造インスタンスへのリンクの数についてまったく心配するべきではありません。そうしないと、膨大な数の追加の(コード自体に直接表示されない)制限と要件がないと、コードの一部が汎用的で透明になりません。このようなコードをより高いレベルのコードに構成することはできません。これは、責任分離の原則(ソフトウェアエンジニアリングの主要な構成要素であり、残念ながらほとんどの低レベルの開発者によってまったく尊重されない)の容認できない違反です。


1
@Mason Wheeler、C ++でさえ、非常に限られた形式のクロージャーを実装します。しかし、ほぼ適切な、一般的に使用可能な閉鎖ではありません。
SKロジック

1
あなたが間違っている。スタック変数を参照できないという事実からGCを保護することはできません。それは面白いです。C++では、「適切に自動的に破棄される動的に割り当てられた変数へのポインターのコピー」アプローチも使用できます。
-DeadMG

1
@DeadMG、あなたのコードはあなたが上に構築する他のレベルを通して低レベルのエンティティをリークしているのを見ませんか?
SKロジック

1
@ SK-Logic:OK、用語の問題があります。「本当のクロージャ」の定義は何ですか?Delphiのクロージャではできないことを何ができるのでしょうか?(定義にメモリ管理に関する情報を含めることで、目標の投稿を移動します。実装の詳細ではなく、動作について説明しましょう。)
Mason Wheeler

1
@ SK-Logic:...そして、Delphiのクロージャーでは達成できない単純な型指定のないラムダクロージャーでできることの例はありますか?
メイソンウィーラー

2

本当に、あなた自身のメモリを管理することは、バグのもう一つの潜在的な原因です。

呼び出しfree(または使用している言語の同等のもの)を忘れた場合、プログラムはすべてのテストに合格できますが、メモリはリークします。適度に複雑なプログラムでは、への呼び出しを見落とすのは非常に簡単freeです。


3
逃したことfreeは最悪のことではありません。早期freeははるかに破壊的です。
ハービー

2
そしてダブルfree
quant_dev

へへ!上記の2つのコメントの両方に沿って説明します。私はこれらの違反の1つを自分で犯したことはありません(私が知る限り)が、その影響がどれほどひどいものであるかを見ることができます。quant_devからの答えはそれをすべて言っています-メモリ割り当てと割り当て解除の間違いは、見つけて修正するのが難しいことで有名です。
ダウッドはモニカを復活させると

1
これは誤りです。「1970年初頭」と「1990年後半」を比較しています。その時点で存在していたのGC mallocfree、外出先への非GCの方法でしたが何のために有用であることが非常に遅すぎました。RAIIのような最新の非GCアプローチと比較する必要があります。
DeadMG

2
@DeadMG RAIIは手動のメモリ管理ではありません
quant_dev

2

手動リソースは退屈なだけでなく、デバッグも困難です。言い換えれば、それを正しくするのは退屈なだけでなく、間違えたときに問題がどこにあるのかは明らかではありません。これは、たとえばゼロによる除算とは異なり、エラーの影響がエラーの原因から離れて現れ、ドットを接続するには時間、注意、および経験が必要だからです。


1

ガベージコレクションは、1つの大きな進歩の一部である以外に、GCとは関係のない言語の改善に対して多くの功績があると思います。

私が知っているGCの確かな利点の1つは、プログラム内でオブジェクトを自由に設定できることと、すべてのオブジェクトが処理されるとオブジェクトがなくなることです。別のクラスのメソッドに渡すことができ、心配する必要はありません。他のどのメソッドに渡されるか、他のクラスがそれを参照するかは気にしません。(メモリリークは、オブジェクトを作成したクラスではなく、オブジェクトを参照するクラスの責任です。)

GCがなければ、割り当てられたメモリのライフサイクル全体を追跡する必要があります。アドレスを作成したサブルーチンからアドレスを上下に渡すたびに、そのメモリへの制御不能な参照があります。悪い昔は、スレッドが1つしかなくても、再帰と通常のオペレーティングシステム(Windows NT)により、割り当てられたメモリへのアクセスを制御できませんでした。私は無料でリグしなければなりませんでしたすべての参照がクリアされるまで、メモリブロックをしばらく保持するために、自分の割り当てシステムでメソッドする必要がありました。開催時間は純粋な推測でしたが、うまくいきました。

それが私が知っている唯一のGCの利点ですが、それなしでは生きていけませんでした。どんな種類のOOPもそれなしでは飛ぶとは思いません。


1
DelphiとC ++は、GCのないOOP言語として非常に成功しています。「制御不能な参照」を防ぐために必要なのは、ちょっとした規律です。単一所有権の原則を理解している場合(私の答えを参照)、ここで話している問題は完全に非問題になります。
メイソンウィーラー

@MasonWheeler:所有者オブジェクトが解放されるとき、所有オブジェクトが参照されているすべての場所を知る必要があります。この情報を維持し、それを使用して参照を削除することは、非常に多くの作業のように思えます。参照がまだ完全にクリアできないことがよくありました。所有者を削除済みとしてマークし、定期的に実行して、安全に自分自身を解放できるかどうかを確認する必要がありました。Delphiを使用したことは一度もありませんが、実行効率を少し犠牲にして、C#/ Javaを使用するとC ++よりも開発時間が大幅に短縮されました。(すべてがGCによるものではありませんが、助けられました。)
ラルフチャピン

1

物理的な漏れ

GCが対処する種類のバグは、(少なくとも外部の観察者にとっては)自分の言語、ライブラリ、概念、イディオムなどをよく知っているプログラマーがしないようなことのように見えます。しかし、私は間違っている可能性があります:手動メモリ処理は本質的に複雑ですか?

メモリ管理を可能な限り手動で発音し、極端なものを比較する(C ++はほとんどGCなしでメモリ管理を自動化する)Cエンドから来ると、GCと比較するという意味で「実際にはない」と言います来るリーク。初心者や時にはプロでさえfree、与えられたものを書くのを忘れるかもしれませんmalloc。それは間違いなく起こります。

しかし、valgrindリーク検出などのツールがあり、コードの実行時に、そのようなミスがコードの正確な行までいつ/どこで発生したかを即座に見つけます。それがCIに統合されると、そのような間違いをマージすることはほぼ不可能になり、修正するのは簡単になります。したがって、合理的な基準を備えたチーム/プロセスでは決して大したことではありません。

確かに、free呼び出されなかったテストのレーダーの下で飛ぶエキゾチックな実行ケースがいくつかあるかもしれません。おそらく破損したファイルのようなあいまいな外部入力エラーが発生すると、システムが32バイトか何かをリークする可能性があります。かなり良いテスト標準とリーク検出ツールの下でもそれは間違いなく起こると思いますが、ほとんど起こらないことで少しのメモリをリークすることもそれほど重要ではありません。以下の一般的な実行パスであっても、GCで防止できない方法で大量のリソースをリークする可能性のある、はるかに大きな問題が発生します。

また、おそらく別のスレッドによって何らかの形の遅延/非同期処理のためにオブジェクトのライフタイムを延長する必要がある場合、GCの疑似形式(参照カウントなど)に似たものがなければ困難です。

宙ぶらりんのポインター

メモリ管理のより多くの手動形式の本当の問題は、私にはリークではありません。CまたはC ++で記述されたネイティブアプリケーションのうち、本当に漏れやすいものはどれくらいあるでしょうか。Linuxカーネルは漏れやすいですか?MySQL?CryEngine 3?デジタルオーディオワークステーションとシンセサイザー?Java VMはリークしますか(ネイティブコードで実装されていますか)?Photoshop?

どちらかといえば、私たちが見回すと、最もリークの多いアプリケーションはGCスキームを使用して記述されたものになる傾向があると思います。しかし、それがガベージコレクションのバタンとされる前に、ネイティブコードにはメモリリークとはまったく関係のない重要な問題があります。

私にとっての問題は常に安全でした。私たちもfreeポインターを介し記憶する、リソースへの他のポインターがある場合、それらはぶら下がり(無効化された)ポインターになります。

これらのぶら下がりポインタのポインティにアクセスしようとすると、未定義の動作に陥りますが、ほとんどの場合、セグメンテーション違反/アクセス違反が発生してすぐにクラッシュします。

上記のすべてのネイティブアプリケーションには、主にこの問題が原因でクラッシュを引き起こす可能性のある不明瞭なエッジケースが1つまたは2つあります。これは主にこの問題によるものです。

...それはリソース管理のためですは、GCを使用するかどうかに関係なく、が難しい。実際の違いは、多くの場合、リソースの誤った管理につながるミスに直面した場合のリーク(GC)またはクラッシュ(GCなし)です。

リソース管理:ガベージコレクション

複雑なリソース管理は、どんなに難しい手動のプロセスです。GCはここでは何も自動化できません。

このオブジェクト「Joe」がある例を見てみましょう。ジョーは、彼がメンバーである多くの組織から参照されています。彼らは毎月かそこらで彼のクレジットカードから会費を引き出します。

ここに画像の説明を入力してください

また、ジョーの生涯をコントロールするためのジョーへの言及もあります。プログラマーとして、ジョーはもう必要ないとしましょう。彼は私たちを悩ませ始めており、彼が所属しているこれらの組織は、彼に対処する時間を無駄にする必要がなくなりました。そこで、私たちは彼のライフライン参照を削除することにより、彼を地球の表面から拭き取ろうとします。

ここに画像の説明を入力してください

...しかし、待ってください。ガベージコレクションを使用しています。ジョーへの強い言及はすべて彼を引き留めます。そのため、彼の所属する組織から彼への参照も削除します(彼の登録を解除します)。

ここに画像の説明を入力してください

...フープを除いて、彼の雑誌の購読をキャンセルするのを忘れていました!現在、Joeは記憶に残り、私たちを悩ませ、リソースを使い果たしています。また、雑誌会社は、Joeのメンバーシップを毎月処理し続けています。

これは、ガベージコレクションスキームを使用して記述された多くの複雑なプログラムがリークし、実行時間が長くなるにつれてメモリを使い果たし、処理が増える可能性がある主な間違いです(定期的な雑誌購読)。それらは、これらの参照の1つ以上を削除するのを忘れていたため、プログラム全体がシャットダウンされるまで、ガベージコレクターが魔法をかけることができませんでした。

ただし、プログラムはクラッシュしません。それは完全に安全です。それはちょうどメモリを独占し続け、ジョーはまだ残っています。多くのアプリケーションでは、問題にメモリ/処理を追加するだけのこの種の漏れやすい動作は、特に今日のマシンのメモリと処理能力を考えると、ハードクラッシュよりもはるかに望ましい場合があります。

リソース管理:手動

次に、Joeへのポインタと手動メモリ管理を使用する代替案を考えてみましょう。

ここに画像の説明を入力してください

これらの青いリンクは、ジョーの生涯を管理しません。地球の表面から彼を削除したい場合、次のように手動で彼を破壊するように要求します。

ここに画像の説明を入力してください

今では通常、あちこちにぶら下がっているポインターが残っているので、Joeへのポインターを削除しましょう。

ここに画像の説明を入力してください

...まったく同じ間違いを犯し、ジョーの雑誌購読を解除するのを忘れていました!

今を除いて、宙ぶらりんのポインターがあります。雑誌の購読がJoeの月額料金を処理しようとすると、全世界が爆発します。通常、即座にハードクラッシュが発生します。

開発者がリソースへのすべてのポインター/参照を手動で削除するのを忘れた、これと同じ基本的なリソースの誤った管理ミスにより、ネイティブアプリケーションで多くのクラッシュが発生する可能性があります。この場合、しばしば完全にクラッシュするため、通常、実行時間が長くなってもメモリを占有しません。

現実の世界

さて、上記の例はとてつもなくシンプルな図を使用しています。実際のアプリケーションでは、シーングラフに格納された数百の異なるタイプのリソース、それらのいくつかに関連付けられたGPUリソ​​ース、他のユーザーに結び付けられたアクセラレーター、数百のプラグインに分散されたオブザーバーを含む、グラフ全体をカバーする数千の画像が必要になる場合がありますシーン内のいくつかのエンティティタイプの変化を監視し、観測者が観測者を観察し、音声がアニメーションに同期しているなど。上記の間違いを回避するのは簡単に思えるかもしれませんが、実世界では一般的にこれほど単純ではありません数百万行のコードにまたがる複雑なアプリケーションの本番コードベース。

いつか誰かがそのコードベースのどこかでリソースを誤って管理する可能性は非常に高くなる傾向があり、その可能性はGCの有無にかかわらず同じです。主な違いは、この間違いの結果として何が起こるかであり、これは、この間違いを発見して修正する速さにも影響する可能性があります。

クラッシュ対リーク

今、どれが悪いですか?すぐにクラッシュするのか、Joeが不思議なままに残るサイレントメモリリークですか?

ほとんどが後者に答えるかもしれませんが、このソフトウェアは数時間、場合によっては数日間実行されるように設計されているとしましょう。追加したこれらのJoeとJaneのそれぞれは、ソフトウェアのメモリ使用量を1ギガバイト増やします。それはミッションクリティカルなソフトウェアではなく(クラッシュは実際にユーザーを殺すことはありません)、パフォーマンスにクリティカルなソフトウェアです。

この場合、デバッグ時にすぐに表示されるハードクラッシュは、あなたが犯した間違いを指摘し、テスト手順のレーダーの下を飛ぶ可能性のある漏れやすいソフトウェアよりも実際に望ましい場合があります。

一方、パフォーマンスが目標ではないミッションクリティカルなソフトウェアであり、可能な限りクラッシュしない場合、実際にはリークが望ましい場合があります。

弱い参照

弱参照として知られるGCスキームには、これらのアイデアのハイブリッドがあります。弱参照を使用すると、これらすべての組織にJoeを弱参照させることができますが、強参照(Joeの所有者/ライフライン)がなくなったときに彼が削除されるのを防ぐことはできません。それにもかかわらず、Joeがこれらの弱い参照を介してもはや存在しないことを検出できるという利点が得られ、簡単に再現可能な種類のエラーを取得することができます。

残念なことに、弱参照はおそらく使用されるほどには使用されないため、多くの複雑なGCアプリケーションは、複雑なCアプリケーションよりもはるかにクラッシュしにくい場合でも、リークの影響を受けやすい場合があります。

いずれにせよ、GCがあなたの人生を楽にするか難しくするかは、あなたのソフトウェアがリークを避けることがいかに重要か、そしてこの種の複雑なリソース管理を扱うかどうかに依存します。

私の場合、リソースが数百メガバイトからギガバイトに及ぶパフォーマンスが重要な分野で働いていますが、上記のような間違いのためにユーザーがアンロードを要求したときにそのメモリを解放しないことは、実際にはクラッシュよりも好ましくありません。クラッシュは簡単に発見して再現できるため、ユーザーにとって最も嫌いなものでなくても、多くの場合プログラマーのお気に入りのバグになります。これらのクラッシュの多くは、ユーザーに届く前に健全なテスト手順で表示されます。

とにかく、これらはGCと手動メモリ管理の違いです。あなたの直接の質問に答えるために、手動のメモリ管理は難しいと思いますが、リークとはほとんど関係がなく、リソース管理が重要でない場合、GCと手動のメモリ管理の両方は依然として非常に困難です。GCはおそらく、プログラムが正常に動作しているように見えますが、ますます多くのリソースを消費している、よりトリッキーな動作をしています。手動のフォームはそれほどトリッキーではありませんが、上記のようなミスでクラッシュして大きな時間を費やすことになります。


-1

メモリを扱う際にC ++プログラマが直面する問題のリストを以下に示します。

  1. スコープ割り当ての問題は、スタック割り当てメモリで発生します。有効期間は、割り当てられた関数の外側にはなりません。この問題には、ヒープメモリ、および呼び出しスタック内で割り当てポイントを上に移動する、またはオブジェクト内から割り当てる3つの主な解決策があります。
  2. Sizeofの問題は、割り当てられたスタックと、内部オブジェクトおよび部分的にヒープに割り当てられたメモリからの割り当てにあります。メモリブロックのサイズは、実行時に変更できません。ソリューションは、ヒープメモリアレイ、ポインタ、ライブラリ、およびコンテナです。
  3. 定義の問題の順序は、オブジェクト内から割り当てる場合です。プログラム内のクラスは正しい順序である必要があります。ソリューションは、依存関係をツリーに制限し、クラスを再配列し、前方宣言、ポインターとヒープメモリを使用せず、前方宣言を使用します。
  4. 内外の問題は、オブジェクトに割り当てられたメモリにあります。オブジェクト内のメモリアクセスは2つの部分に分割されます。一部のメモリはオブジェクトの内部にあり、他のメモリはオブジェクトの外部にあります。ソリューションが決定を正しく行っているか、ポインタとヒープメモリがあります。
  5. 再帰オブジェクトの問題は、オブジェクトに割り当てられたメモリにあります。同じオブジェクトが内部に配置されている場合、オブジェクトのサイズは無限になり、ソリューションは参照、ヒープメモリ、およびポインタになります。
  6. 所有権追跡の問題は、ヒープに割り当てられたメモリにあります。ヒープに割り当てられたメモリのアドレスを含むポインタは、割り当てポイントから割り当て解除ポイントに渡される必要があります。ソリューションは、スタック割り当てメモリ、オブジェクト割り当てメモリ、auto_ptr、shared_ptr、unique_ptr、stdlibコンテナです。
  7. 所有権の重複の問題は、ヒープに割り当てられたメモリにあります。割り当て解除は1回しか実行できません。ソリューションは、スタック割り当てメモリ、オブジェクト割り当てメモリ、auto_ptr、shared_ptr、unique_ptr、stdlibコンテナです。
  8. ヌルポインターの問題は、ヒープに割り当てられたメモリにあります。ポインターをNULLにすると、実行時に多くの操作がクラッシュします。ソリューションは、スタックメモリ、オブジェクト割り当てメモリ、およびヒープ領域と参照の慎重な分析です。
  9. メモリリークの問題は、ヒープに割り当てられたメモリにあります。割り当てられたメモリブロックごとにdeleteの呼び出しを忘れます。ソリューションは、valgrindのようなツールです。
  10. スタックオーバーフローの問題は、スタックメモリを使用している再帰的な関数呼び出しの場合です。通常、再帰アルゴリズムの場合を除き、スタックのサイズはコンパイル時に完全に決定されます。OSのスタックサイズを誤って定義すると、スタックスペースに必要なサイズを測定する方法がないため、この問題が発生することがよくあります。

ご覧のとおり、ヒープメモリは非常に多くの既存の問題を解決していますが、さらに複雑になります。GCは、その複雑さの一部を処理するように設計されています。(いくつかの問題名がこれらの問題の正しい名前ではない場合はごめんなさい-正しい名前を把握するのが難しい場合があります)


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