質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.52%

  • C#

    8795questions

    C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

C#のデバッグやメモリの管理について理解を深めたい

解決済

回答 6

投稿

  • 評価
  • クリップ 2
  • VIEW 1,509

tkow

score 1177

お世話になっております.

独学でC#のコードを書くことが多く,一度ちゃんと言語仕様を理解しようと思い,日本語訳もあるのでEffectiveC#4.0を読むことにしました。
かなり難解だと思うことが多く, 半分くらいしか理解できていないと思っています。

ただ一回ざっと読んだ印象では逆に今まで自分の書いていたコードが恐ろしくなり, 読んでおいてよかったと思うことが沢山書いて合った気がします。
C#のコンパイラやメモリ管理およびその他の知識を拡充したいため何かオススメの書籍や情報源を教えていただけないでしょうか。

特に以下の項目を勉強したいです。

  • CLR
  • IL
  • IDE拡張
  • 逆アセンブル
  • デバッグ機能
  • ガベージコレクタ
  • 副作用
  • 共変性/反変性

今まで読んだ本は以下の通りです。

  • VisualC#パーフェクトマスター 2013

  • C#ショートプログラミング

  • C#によるマルチコアのための非同期/並列処理プログラミング

  • 究極のC#プログラミング~新スタイルによる実践的コーディング 

  • C#6.0 Cook Book(洋書)

特に参照消し忘れのようなバグに悩まされているため, メモリの動きがどのようになっているか見る方法や, 参照している変数の調べ方などデバッグに重点を置いた内容の情報を探しています。よろしくお願いいたします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 6

+5

こんにちは。

特に参照消し忘れのようなバグに悩まされている

これがもし参照型変数を代入して、コピーのつもりが共有してしまったバグでしたら、十分な対処方法はないような気がしてます。

軽減するために、C言語のポインタをきっちりマスターすることは有用と思います。特に構造体へのポインタ変数はC#のクラスの参照型変数とそこそこ近いです。多くの人が躓くポイントですが、高速なプログラムを書くためには必須スキルと思います。
そして、C#やJavaはこの部分覆い隠してますが、結局使っているので理解しないとまれにハマって抜け出せなくなる部分と思います。(知っていてもハマりますから、知らないと悲惨かと。)

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/02/13 14:40

    C#やJavaは正しい手順で全て組むことを大前提としてる感があるなあと思ってます。
    サーバーサイド処理で、長時間、沢山運用しない限りそれで悪さはしないわけですが…
    想像が届かないことがしばしばあって、微妙なトコですね。

    守るべきルール、をnugetからダウンロードしたライブラリが守ってなかったりしたら…ってこともありますしね。

    キャンセル

  • 2017/02/13 15:44 編集

    >(知っていてもハマりますから、知らないと悲惨かと。)
    参照型と参照値渡しと参照渡しの違いは理解していてポインタもポインタのポインタのswapとか関数ポインタ引き摺り回したり出来るくらいには経験あるのですがほんとこの状態ですね。

    nullとかnewとかで一時変数の参照先変更されないように気をつけなければいけないとかいうことは分かっていて,敢えて参照型引数で参照先を操作しているのですが,参照を変えないことに気をつけてても,参照消し忘れで辛い目にあっています。C++のスマートポインタのように参照カウントとかわかる仕組みはC#だと自分で作るしかないんでしょうか。

    親を自己参照型クラス,子をdictionary実装でノードツリーをつくっていて途中のノードで親から子のノードへの参照を消して安心してたら,childrenからparentのノードの参照を消し忘れていてGCに入ってなかったため親から辿ったノードと子から遡った同じ階層のノードの参照先が変わって盛大にバグったという経緯があってどのオブジェクトから参照されてるのか、あるいはガベージコレクタに入ったオブジェクトを常に監視する方法などが合ったらいいなと思い調べています。

    キャンセル

  • 2017/02/13 16:57

    tkowさん。

    この問題は値型の変数(int型など)は代入文で値がコピーされ、参照型の変数(クラスのインスタンス)は代入文でアドレスがコピーされる問題です。
    どちらも見た目が全く同じで振る舞いが異る点が問題と思います。

    tkowさんのケースは子ノードへの参照が親からのもの以外(1)に残っていて、その参照経由でアクセスしたため、親ノードが参照している子ノードと異なるノード群にアクセスしてしまったと言う問題と思います。
    (1)がどのような経緯で残ったのか分かりませんが、子ノード自体の内容をコピーしたつもりが実は子ノードへの参照をコピーした的なものかも知れませんね。(ディープコピーが必要なのにシャローコピーしてしまった等)

    この場合、代入文で内容をコピーするものとアドレスをコピーするものが入り交じることがバグの根本原因と思いますが、その2つを区別させないことがC#やJavaの「仕様」なので、本質的には解決不能と思います。
    無いよりましな対策としては、変数名で値型か参照型を区別できるような命名規則にすることくらいしか思いつきません。(例えば前者は v 始まり、後者は r 始まりなど)

    > C++のスマートポインタのように参照カウントとかわかる仕組み

    deleteを書けないC#では現実的ではないように思います。
    usingとIDisposeを使ってクラスを明示的にデストラクトできても、クラスのメンバ変数がクラス型だった時、それを明示的にデストラクトできないと厳しいです。

    > どのオブジェクトから参照されてるのか、

    参照元を常に弱参照で監視するような仕組みを作れればよいのでしょうが、C#は代入演算子をオーバーロードできないようなので、厳しそうです。

    > あるいはガベージコレクタに入ったオブジェクトを常に監視する方法などが合ったらいいな

    ガベージコレクションされれば、デストラクタが呼ばれるのでその時点でなら判定する方法はあると思います。でも、ガベージコレクションされるまで分かりませんので、実際に必要なタイミングで判定するのは難しそうな印象です。

    キャンセル

  • 2017/02/13 19:36 編集

    Chironianさん

    色々なご意見ありがとうございます。

    > tkowさんのケースは子ノードへの参照が親からのもの以外(1)に残っていて、その参照経由でアクセスしたため、親ノードが参照している子ノードと異なるノード群にアクセスしてしまったと言う問題と思います

    はい。これはその通りで原因はノードを消す際にそのノードのchildrenのそれぞれのparentをnullにしなかったことで消されるノードへの参照が残ってしまっていたことと,そのあと削除されていると思って同じ場所のノードを元親のchildrenの要素としてnewで追加していたので,元々のノードより下にあったノードは消えず(これは高速アクセスのために全てのノードにIDをつけて即座に参照するDictionaryを作成してそのノードの参照は条件で消していないため)に保存されて, 新たに追加したノードは,別にノード伸ばしてしまうということが起きていました。(ノードを消す条件分岐も実はそもそも末端のノードだけを想定していてたまたま中間のノードでも条件に引っかかってしまうことがあり消えるということが起きていて,その挙動は全く予想していなかったので)それに気づくまでにすごく時間がかかったのでどうすれば早く気付けたのか、そもそもどういう設計にすればよかったのか答えがモヤモヤしている感じですね。

    参照型の引数は参照そのものを渡すのではなく,単に一時変数を生成してそこにアドレスの参照を渡すだけなので,nullやnewの参照代入で参照先が変わってしまうと,それ以降の代入元のオブジェクトの操作が無効になるのは知っていてさらに,一時変数の参照をグローバルオブジェクトの参照先に代入してしまうと,こちらはaliasとして同一視されて保持されてしまうので関数の実行終了での一時変数の解放と同時にグローバルオブジェクトのアドレスの解放が起きてしまうことまで理解しています.そのため,関数内でrefを用いずlistなどを初期化したい場合はnewを使ってはならずclearメソッドなどが用意されている認識です。なので,アドレスコピーと値コピーを混同して操作してるオブジェクトに混乱をしているわけではないですね。

    結構ズボラな性格なので,参照消し忘れって気をつけていても,どうしてもしてしまいそうで,その時点に参照している変数を調べる方法わかってたら,それら全部をnullにしてしまえば,開発楽でいいなくらいの気持ちでした。

    しかし結構簡単にできそうで,意外とできないのですね。なるべく参照管理のコードが分散しないことを意識するべきだと思いました。

    キャンセル

  • 2017/02/13 22:44

    どうも会話が噛み合っていないような印象です。

    > ノードを消す条件分岐も実はそもそも末端だけのノードを想定していてたまたま中間のノードでも条件に引っかかってしまい消えるということが起きていてその挙動は全く予想していなかったので

    末端のノードだけを削除するロジックを使って、中間のノードを間違って消していたため、中途半端な削除になってしまったと言うことですね?
    その単純なバグの修正に思いの外時間がかかったので、どうやればもっと早く見抜けたか?ということと理解しました。

    ツリーの不具合は頭痛いです。小さなツリーならダンプすればどうにかなりますが、巨大なツリーだとどうにも。時間がかかったこと自体は致し方ないのでは?
    事前に結果を知っていてれば、ツリー操作ライブラリをもっと頑健に開発しておけば良かったことが分かりますが、工数が不足する中その判断をすることは難易度高いと思います。

    簡単そうに見えてツリーは意外に難しいです。どうもオブジェクト指向とは相性が悪いのではないかと私は最近感じてます。

    キャンセル

  • 2017/02/14 09:58 編集

    > どうも会話が噛み合っていないような印象です。

    すみません。だとすれば私の前提の説明不足だと思います。実際なぜこういう現象に直面したかという経緯を1から書くとすごく長く複雑になってしまうので色々前提を端折ってしまってる部分があると思います。

    > ツリーの不具合は頭痛いです。小さなツリーならダンプすればどうにかなりますが、巨大なツリーだとどうにも。時間がかかったこと自体は致し方ないのでは?

    やっぱりそうですか。ありがちなバグですが起きた現象の深刻度が高かったのでなんとかしたいなと思う一方で特効薬はないかんじですかね。

    このバグの怖かったところはノード削除のアルゴリズムは末端ノードには子が存在するはずないという想定で, 末端ノードへの参照を切れば末端ノードはGCに行くだろうという思っていた結果,条件考慮がお粗末で中間ノードで子の参照を削除されることがあり,親の参照は消すことがないので,子から遡ると消えたはずのノードがゾンビ化して存在しrootまで参照できてしまうことで木構造が正しく存在しているように見えてしまうことと,親から遡ると子の参照は削除されてるか新しいノードが接続されているので,想定していない末端にたどり着いてしまうことが起きました(子は実装はdictionaryですが実際はインデクサのようにindex管理されてます)。

    割とこういうことが起きたのが衝撃的だったので, 後学のために意見いただけてよかったです。ありがとうございます。

    キャンセル

  • 2017/02/14 11:54

    横失礼
    循環参照はしばしば問題を引き起こすので、可能な限り子供に親の参照を持たせないようにしますね。
    例えば、親で子供を監視するようにします。また下記ルールを守ります。
    1.特殊な親子関係は汎用コレクションをラップして親子関係専用のコレクションにする
    2.コレクションの操作が複雑な場合、ラップクラスにメソッドとして設ける
    3.ユニットテストを書く(GC含む)
    C#はきっちりとしたことを1つずつやっていくこと前提の言語という印象です。

    キャンセル

  • 2017/02/14 13:13

    やっと理解できた気がします。

    C/C++は不正メモリにアクセスした時、規格上は未定義ですが落としてくれる場合が多いです。参照を消し忘れると落ちるわけです。運良くバグを見つけやすいということかも知れません。(これに頼るといつかひどい目に会いますが。)
    それに対してC#は参照を消し忘れても絶対に落ちませんね。

    参照の消し忘れは本来あってはならないことです。でもバグはいつでも忍び込みます。C/C++は運がよければ落ちてくれるけど、C#は絶対落ちないので参照消し忘れバグの検出に苦しんでいるということですね。
    なるほど。確かに頭の痛い問題です。

    参照を全て消すという消極的な削除ではなく、インスタンスを明示的に削除とマークして、そこへアクセスしたら落ちるような機能があればよいですね。
    言語仕様上C#はそのような機能をサポートしていなので、プログラマ側で作るしかないです。ノード・クラスにそのような仕掛けを仕込むのがベストかも知れません。

    キャンセル

  • 2017/02/16 02:27 編集

    haru666さん

    > 循環参照はしばしば問題を引き起こすので、可能な限り子供に親の参照を持たせないようにしますね。
    例えば、親で子供を監視するようにします。また下記ルールを守ります。
    1.特殊な親子関係は汎用コレクションをラップして親子関係専用のコレクションにする
    2.コレクションの操作が複雑な場合、ラップクラスにメソッドとして設ける
    3.ユニットテストを書く(GC含む)

    循環参照って怖いですね。やらないほうがいいのは知っていましたが今回は身に沁みました。ただし親を参照する必要はあるのでテストで確認するのが現実的ですね。(今回も最小構成ケースでテスト書きました)

    > C#はきっちりとしたことを1つずつやっていくこと前提の言語という印象です。

    こちら凄くよくわかります。そもそも仕様上ハマるポイントがいくつもあるのはそういう実装は推奨されないということなのでしょうね。(やろうと思えばunsafeコードでポインタ使えますが・・・。)

    Chironianさんがおっしゃっていることもとても参考になりました。確かに落ちる仕組みがあれば参照バグに気づけますね。

    ただし、自分で作らなければいけないレベルになってしまうということは前おっしゃっていたようにオブジェクト指向的に(もしくはC#での実装)との相性が悪いのかもしれませんね。

    慣れると凄く書きやすい反面、コードやメモリ参照の仕組みを理解していないと罠にはまりやすい言語だと思います。最近まではもう長いことC#触ってるので、テクニカルなコードは書けなくてもやってはいけないコードを書かない自負はあったのですが今回の件はいい薬になりました。

    ご意見ありがとうございました。

    キャンセル

checkベストアンサー

+2

Visual Studioのデバッグで物足りないのであれば、windbgでsos拡張を使用するのがおすすめです。
Debugging Managed Code Using the Windows Debugger
windbg sos で検索すれば解説サイトがいくつか出てきます。

特に参照消し忘れのようなバグに悩まされているため, メモリの動きがどのようになっているか見る方法や, 参照している変数の調べ方などデバッグに重点を置いた内容の情報を探しています。

!DumpHeapや!gcrootで調べることができます。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/02/14 10:47

    この情報素晴らしいですね。
    まさに欲しかった情報でした。
    なのでベストアンサーとさせていただきます。

    キャンセル

+1

Roslyn の日本語で読める解説が多い、岩永さんのhttp://ufcpp.net/blog/ は見ていますね。
超有名ですけど。

参照の消し忘れというと弱参照でしょうか?
http://ufcpp.net/study/csharp/RmWeakReference.html

あと、今時は、ソースコードがオープンなので、それを読むのもいいと思います。
http://qiita.com/kiichi54321/items/1699f3182ad3665dc7de

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/02/13 15:48 編集

    ありがとうございます。読みます!
    やはり弱参照の仕組みを作るのが確実ですかね。
    pythonだと当たり前に使いますがC#だとメモリの解放はGCに任せる方がパフォーマンス的にはいいというので,ちょっと消極的な選択肢でしたが,検討しますね。

    キャンセル

  • 2017/02/13 17:09

    弱参照自体は、どうしてもこういう実装しないとダメ、というものでしかやらないほうがいいですね。
    設計で回避できるものがほとんどだと思います。

    そういえば、デバッグ環境としては、テストプロジェクトは便利ですよ。 http://qiita.com/kiichi54321/items/33238ec7e8a5c95b5026 

    キャンセル

  • 2017/02/13 19:47

    ありがとうございます。テストプロジェクト使ってます。
    知らないと消耗するだろうなぁと思って使ってますね。

    キャンセル

+1

僕はそんなに色々な本を読んでる方じゃないですし、僕が実際に読んだのは第3版なので確実とは言えませんが
プログラミング.NET Framework 第4版は良い本かなと。

少し値段が高いし、もうC# 7.0が囁かれる中でC#5時代の本っていうのがなんかアレですが。
HOW TO本ではないので、デバッグ手法やIDE拡張なんかは入ってないです。
また、.NET Framework…というかCLRの話なので直接的なILの話はほとんど入っていません。
組んだコードがどのように実行されるのか、ということの解説本です。
.NETの文字列の仕組みだったり、ロック、ガベージコレクタの細かい動作を僕に教えてくれたのはこの本です。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/02/13 15:47

    ありがとうございます!読んでみます!

    キャンセル

+1

定番&無料なものとしては本家のドキュメントをですかね。

(msdn)共通言語ランタイム (CLR)

(msdn).NET Framework 4.6 および 4.5

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/02/13 15:47

    ありがとうございます!特に知りたい項目が詳しく書かれてそうなので読みます。

    キャンセル

  • 2017/02/13 15:52

    日本語のものでも物によっては機械翻訳(直訳)があるので原文を読めるなら原文を見るといいですよ。

    キャンセル

  • 2017/02/13 15:58

    ありがとうございます。私も明らか日本語役がおかしい著書に対しては基本的に原文読んでいます。

    キャンセル

+1

もう、古い本になって入手しづらいかもしれませんが、  
The Root of .NET Framework  
は読んでおくべきかと思います。  
.NET の中や、デバッグ手法等が日本語で書かれている貴重な本です。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/02/13 15:49

    ありがとうございます。探して読みます。

    キャンセル

同じタグがついた質問を見る

  • C#

    8795questions

    C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。