それとも、「C ++でオブジェクトを破棄するのは非常に難しい-私はそれに20%の時間を費やしていますが、メモリリークはよくある場所です」というようなものでしょうか。
C ++やCでの私の個人的な経験では、メモリリークは回避するための大きな苦労ではありませんでした。たとえば、適切なテスト手順とValgrindを使用するとoperator new/malloc
、対応なしのへの呼び出しによって引き起こされた物理的なリークdelete/free
は、多くの場合、迅速に検出および修正されます。公平を期すために、大規模なCまたは古い学校のC ++コードベースにはdeleting/freeing
、テストのレーダーの下で飛んだエッジケースに存在しない結果として、物理的に数バイトのメモリリークがある物理的にあいまいなエッジケースがある可能性があります。
しかし、実際的な観察としては、私が遭遇する最も漏れやすいアプリケーション(作業しているデータ量が増えていなくても、実行時間が長くなるほど多くのメモリを消費するアプリケーションなど)は、通常CまたはC ++。LinuxカーネルやUnreal Engine、さらにはJavaの実装に使用されるネイティブコードのようなものは、遭遇するリークの多いソフトウェアのリストの中に見つかりません。
私が遭遇する傾向のある最も有名な種類の漏出ソフトウェアは、ガベージコレクションを使用しているにもかかわらず、FlashゲームなどのFlashアプレットのようなものです。そして、これから何かを推測するのであれば、それは公平な比較ではありません。多くのFlashアプリケーションは、健全なエンジニアリングの原則とテスト手順を欠いている新進の開発者によって書かれているためです(同様に、GCを扱う熟練した専門家がいると確信しています)。リーキーなソフトウェアに苦労しないでください)、しかし、GCがリーキーなソフトウェアの作成を阻止していると考える人には、言いたいことがたくさんあります。
ぶら下がりポインタ
私の特定のドメイン、経験、そして主にCとC ++を使用しているように(そして、GCの利点は私たちの経験とニーズによって変わると思います)、GCが私のために解決する最も直接的なことは実用的なメモリリークの問題ではありませんが、ぶら下がりポインタアクセス。これは、文字どおり、ミッションクリティカルなシナリオでの命の恩人になる可能性があります。
残念ながら、GCが未解決のポインターアクセスを解決する多くの場合、GCは同じ種類のプログラマーのミスを論理メモリリークに置き換えます。
新登場のコーダーによって作成されたFlashゲームを想像すると、彼はゲーム要素への参照を複数のデータ構造に格納し、それらがこれらのゲームリソースの所有権を共有するようにする可能性があります。残念ながら、次のステージに進むときにデータ構造の1つからゲーム要素を削除するのを忘れて、ゲーム全体がシャットダウンされるまで解放されないという間違いを犯したとします。ただし、要素が描画されていないか、ユーザーの操作に影響を与えていないため、ゲームはまだ正常に動作しているように見えます。それにもかかわらず、フレームレートがスライドショーに対応している間、ゲームはますます多くのメモリを使用し始めますが、隠された処理は、ゲーム内のこの隠された要素のコレクションをループします(現在、爆発的なサイズになっています)。これは、私がこのようなFlashゲームで頻繁に遭遇する問題です。
- アプリケーションを閉じるときにメモリが解放されているため、これは「メモリリーク」とは見なされず、代わりに「スペースリーク」またはこの効果の何かと呼ばれる可能性があるという人に遭遇しました。このような区別は問題を特定して話すのに役立つかもしれませんが、対処するときに「メモリリーク」ほど問題ではないように話している場合、このような区別はこのコンテキストではそれほど役立ちません。ソフトウェアを確保するという実際的な目標は、実行時間が長くなってもばかげた量のメモリを占有しないことです(プロセスの終了時にプロセスのメモリを解放しないあいまいなオペレーティングシステムについて話しているのでない限り)。
今、同じ新進の開発者がC ++でゲームを書いたとしましょう。その場合、通常、ゲームにはメモリを「所有」する中心的なデータ構造が1つだけありますが、他のメモリはそのデータを指し示します。彼が同じ種類の間違いを犯した場合、次のステージに進むときに、ぶら下がりポインタにアクセスした結果としてゲームがクラッシュする可能性があります(さらに悪いことに、クラッシュ以外の何かを行います)。
これは、私のドメインでGCとGCなしの間で最も頻繁に遭遇する傾向がある最も直接的な種類のトレードオフです。私のドメインでは、実際にはGCをあまり気にしていません。これは、ミッションクリティカルではありません。以前のチームでGCを無計画に使用して、上記のようなリークを引き起こしていたためです。 。
私の特定のドメインでは、多くの場合、ソフトウェアがクラッシュまたはグリッチアウトすることを実際に好みます。なぜなら、ソフトウェアが30分実行された後、ソフトウェアが不思議なことに大量のメモリを消費している理由を追跡するよりも、検出が少なくともはるかに簡単だからです。単体テストと統合テストは問題なく成功しました(メモリはシャットダウン時にGCによって解放されているため、Valgrindからでも送信されません)。それでも、それは私の側でのGCの非難や、役に立たないまたはそのようなものであると言う試みではありませんが、私がリークしたソフトウェア(逆に、GCを利用する1つのコードベースが、これまでで最もリークの多いリークであるという反対の経験をしました。そのチームの多くのメンバーが公平であるために、弱い参照が何であるかさえ知りませんでした、
共有の所有権と心理学
私が見つけたガベージコレクションで「メモリリーク」が発生しやすくなる問題(そして、「スペースリーク」はユーザーエンドの観点からはまったく同じように動作するように呼びます)の手に注意して使用しない人は、私の経験ではある程度「人間の傾向」に関係しています。そのチームと私が今まで遭遇した最も漏洩しやすいコードベースの問題は、GCがリソースの所有者について考えるのを止めさせるという印象を彼らが受けているように見えることでした。
私たちのケースでは、お互いを参照するオブジェクトが非常に多くありました。モデルは、マテリアルライブラリとシェーダーシステムとともにマテリアルを参照します。マテリアルは、テクスチャライブラリと特定のシェーダーと共にテクスチャを参照します。カメラは、レンダリングから除外する必要があるあらゆる種類のシーンエンティティへの参照を保存します。リストは無期限に続くようでした。これにより、システムの多額のリソースが所有され、アプリケーションの状態の他の10か所以上で一度に延長され、リークにつながるような人為的エラーが非常に発生しやすくなりました(そしてマイナーな問題ですが、私はギガバイト単位で話しており、深刻なユーザビリティの問題があります)。概念的には、これらのすべてのリソースを所有権で共有する必要はなく、概念的にはすべて1人の所有者がいました。
誰がどのメモリを所有しているかについて考えるのをやめ、幸いにも、これについて考えずにオブジェクトへの寿命を延ばす参照をあちこちに保存すると、ポインタがぶら下がってもソフトウェアはクラッシュしませんが、そのような状況下ではほぼ間違いなく不注意な考え方で、追跡が非常に難しく、テストを回避するような方法で、狂ったようにメモリをリークし始めます。
私のドメイン内のぶら下がりポインタに1つの実用的な利点がある場合、それは非常に厄介な不具合やクラッシュを引き起こすことです。そして、少なくとも信頼性のあるものを出荷したい場合、開発者はリソース管理について考え始め、概念的に不要になったオブジェクトへのすべての追加の参照/ポインターを削除するために必要な適切なことを行うようにインセンティブを与える傾向があります。
アプリケーションリソース管理
適切なリソース管理は、リークが深刻なフレームレートと使いやすさの問題を引き起こす永続的な状態が保存されている状態で、長期間有効なアプリケーションでのリークの回避について話している場合のゲームの名前です。そして、ここでリソースを正しく管理することは、GCの有無にかかわらず、同様に困難です。これらの作業は、オブジェクトがポインタであれ、存続期間を延長する参照であれ、不要になったオブジェクトへの適切な参照を削除するためのどちらかの方法であり、手動によるものです。
それは、私delete
たちのことを忘れないで、私の領域での課題new
です(私たちが粗雑なテスト、実践、およびツールでアマチュア時間を話しているのでない限り)。そして、GCを使用しているかどうかを検討する必要があります。
マルチスレッド
ドメインで非常に慎重に使用できる場合、GCで非常に役立つと思われるもう1つの問題は、マルチスレッドコンテキストでのリソース管理を簡略化することです。リソースへの存続期間を延長する参照をアプリケーション状態の複数の場所に保存しないように注意する場合、GC参照の存続期間を延長する性質は、拡張するためにアクセスされるリソースをスレッドが一時的に拡張する方法として非常に役立つ可能性があります。スレッドが処理を完了するために必要な、ほんの短い期間の寿命。
この方法でGCを非常に注意深く使用すると、リークのない非常に正確なソフトウェアが得られると同時に、マルチスレッド化が簡素化されると思います。
GCがない場合でも、これを回避する方法はあります。私の場合、ソフトウェアのシーンエンティティ表現を統合します。クリーンアップフェーズの前に、かなり一般化された方法で一時的にシーンリソースを一時的に拡張するスレッドを使用します。これはGCのように少しにおいがするかもしれませんが、「共有所有権」が関係しておらず、スレッド内の均一なシーン処理設計のみが上記のリソースの破棄を遅らせる点が異なります。それでも、このようなマルチスレッドの場合に、良心的な開発者と非常に慎重に使用でき、関連する永続領域で弱参照を使用する場合は、GCに依存する方がはるかに簡単です。
C ++
最終的に:
C ++では、削除されたオブジェクトをそのライフサイクルの最後に破棄するために、deleteを呼び出す必要があります。
最近のC ++では、これは通常、手動で行うべきことではありません。それをすることを忘れることはそれほどでもありません。画像に例外処理を含める場合、対応するdelete
以下の呼び出しを書き込んでも、コンパイラが挿入した自動デストラクタ呼び出しに依存していないとnew
、何かが途中でスローされてdelete
呼び出しに到達しない可能性があります。君は。
C ++では、例外をオフにして、意図的にスローしないようにプログラムされた特別なライブラリを使用して埋め込みコンテキストのように作業している場合を除き、このような手動のリソースクリーンアップ(dtorの外部でmutexをロック解除するための手動呼び出しの回避を含む)を行わない限り、実際に必要ですたとえば、メモリの割り当て解除だけではありません)。例外処理ではほとんどの場合それが要求されるため、ほとんどの場合、すべてのリソースのクリーンアップはデストラクタを介して自動化する必要があります。