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

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

新規登録して質問してみよう
ただいま回答率
85.50%
C#

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

Q&A

解決済

6回答

3703閲覧

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

退会済みユーザー

退会済みユーザー

総合スコア0

C#

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

3グッド

2クリップ

投稿2017/02/13 01:45

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

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

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

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

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

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

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

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

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

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

  • C#6.0 Cook Book(洋書)

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

iwamoto_takaaki, ai_2013_dev, yohhoy👍を押しています

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答6

0

こんにちは。

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

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

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

投稿2017/02/13 02:34

Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

haru666

2017/02/13 05:40

C#やJavaは正しい手順で全て組むことを大前提としてる感があるなあと思ってます。 サーバーサイド処理で、長時間、沢山運用しない限りそれで悪さはしないわけですが… 想像が届かないことがしばしばあって、微妙なトコですね。 守るべきルール、をnugetからダウンロードしたライブラリが守ってなかったりしたら…ってこともありますしね。
退会済みユーザー

退会済みユーザー

2017/02/13 06:46 編集

>(知っていてもハマりますから、知らないと悲惨かと。) 参照型と参照値渡しと参照渡しの違いは理解していてポインタもポインタのポインタのswapとか関数ポインタ引き摺り回したり出来るくらいには経験あるのですがほんとこの状態ですね。 nullとかnewとかで一時変数の参照先変更されないように気をつけなければいけないとかいうことは分かっていて,敢えて参照型引数で参照先を操作しているのですが,参照を変えないことに気をつけてても,参照消し忘れで辛い目にあっています。C++のスマートポインタのように参照カウントとかわかる仕組みはC#だと自分で作るしかないんでしょうか。 親を自己参照型クラス,子をdictionary実装でノードツリーをつくっていて途中のノードで親から子のノードへの参照を消して安心してたら,childrenからparentのノードの参照を消し忘れていてGCに入ってなかったため親から辿ったノードと子から遡った同じ階層のノードの参照先が変わって盛大にバグったという経緯があってどのオブジェクトから参照されてるのか、あるいはガベージコレクタに入ったオブジェクトを常に監視する方法などが合ったらいいなと思い調べています。
Chironian

2017/02/13 07:57

tkowさん。 この問題は値型の変数(int型など)は代入文で値がコピーされ、参照型の変数(クラスのインスタンス)は代入文でアドレスがコピーされる問題です。 どちらも見た目が全く同じで振る舞いが異る点が問題と思います。 tkowさんのケースは子ノードへの参照が親からのもの以外(1)に残っていて、その参照経由でアクセスしたため、親ノードが参照している子ノードと異なるノード群にアクセスしてしまったと言う問題と思います。 (1)がどのような経緯で残ったのか分かりませんが、子ノード自体の内容をコピーしたつもりが実は子ノードへの参照をコピーした的なものかも知れませんね。(ディープコピーが必要なのにシャローコピーしてしまった等) この場合、代入文で内容をコピーするものとアドレスをコピーするものが入り交じることがバグの根本原因と思いますが、その2つを区別させないことがC#やJavaの「仕様」なので、本質的には解決不能と思います。 無いよりましな対策としては、変数名で値型か参照型を区別できるような命名規則にすることくらいしか思いつきません。(例えば前者は v 始まり、後者は r 始まりなど) > C++のスマートポインタのように参照カウントとかわかる仕組み deleteを書けないC#では現実的ではないように思います。 usingとIDisposeを使ってクラスを明示的にデストラクトできても、クラスのメンバ変数がクラス型だった時、それを明示的にデストラクトできないと厳しいです。 > どのオブジェクトから参照されてるのか、 参照元を常に弱参照で監視するような仕組みを作れればよいのでしょうが、C#は代入演算子をオーバーロードできないようなので、厳しそうです。 > あるいはガベージコレクタに入ったオブジェクトを常に監視する方法などが合ったらいいな ガベージコレクションされれば、デストラクタが呼ばれるのでその時点でなら判定する方法はあると思います。でも、ガベージコレクションされるまで分かりませんので、実際に必要なタイミングで判定するのは難しそうな印象です。
退会済みユーザー

退会済みユーザー

2017/02/13 10:54 編集

Chironianさん 色々なご意見ありがとうございます。 > tkowさんのケースは子ノードへの参照が親からのもの以外(1)に残っていて、その参照経由でアクセスしたため、親ノードが参照している子ノードと異なるノード群にアクセスしてしまったと言う問題と思います はい。これはその通りで原因はノードを消す際にそのノードのchildrenのそれぞれのparentをnullにしなかったことで消されるノードへの参照が残ってしまっていたことと,そのあと削除されていると思って同じ場所のノードを元親のchildrenの要素としてnewで追加していたので,元々のノードより下にあったノードは消えず(これは高速アクセスのために全てのノードにIDをつけて即座に参照するDictionaryを作成してそのノードの参照は条件で消していないため)に保存されて, 新たに追加したノードは,別にノード伸ばしてしまうということが起きていました。(ノードを消す条件分岐も実はそもそも末端のノードだけを想定していてたまたま中間のノードでも条件に引っかかってしまうことがあり消えるということが起きていて,その挙動は全く予想していなかったので)それに気づくまでにすごく時間がかかったのでどうすれば早く気付けたのか、そもそもどういう設計にすればよかったのか答えがモヤモヤしている感じですね。 参照型の引数は参照そのものを渡すのではなく,単に一時変数を生成してそこにアドレスの参照を渡すだけなので,nullやnewの参照代入で参照先が変わってしまうと,それ以降の代入元のオブジェクトの操作が無効になるのは知っていてさらに,一時変数の参照をグローバルオブジェクトの参照先に代入してしまうと,こちらはaliasとして同一視されて保持されてしまうので関数の実行終了での一時変数の解放と同時にグローバルオブジェクトのアドレスの解放が起きてしまうことまで理解しています.そのため,関数内でrefを用いずlistなどを初期化したい場合はnewを使ってはならずclearメソッドなどが用意されている認識です。なので,アドレスコピーと値コピーを混同して操作してるオブジェクトに混乱をしているわけではないですね。 結構ズボラな性格なので,参照消し忘れって気をつけていても,どうしてもしてしまいそうで,その時点に参照している変数を調べる方法わかってたら,それら全部をnullにしてしまえば,開発楽でいいなくらいの気持ちでした。 しかし結構簡単にできそうで,意外とできないのですね。なるべく参照管理のコードが分散しないことを意識するべきだと思いました。
Chironian

2017/02/13 13:44

どうも会話が噛み合っていないような印象です。 > ノードを消す条件分岐も実はそもそも末端だけのノードを想定していてたまたま中間のノードでも条件に引っかかってしまい消えるということが起きていてその挙動は全く予想していなかったので 末端のノードだけを削除するロジックを使って、中間のノードを間違って消していたため、中途半端な削除になってしまったと言うことですね? その単純なバグの修正に思いの外時間がかかったので、どうやればもっと早く見抜けたか?ということと理解しました。 ツリーの不具合は頭痛いです。小さなツリーならダンプすればどうにかなりますが、巨大なツリーだとどうにも。時間がかかったこと自体は致し方ないのでは? 事前に結果を知っていてれば、ツリー操作ライブラリをもっと頑健に開発しておけば良かったことが分かりますが、工数が不足する中その判断をすることは難易度高いと思います。 簡単そうに見えてツリーは意外に難しいです。どうもオブジェクト指向とは相性が悪いのではないかと私は最近感じてます。
退会済みユーザー

退会済みユーザー

2017/02/14 01:00 編集

> どうも会話が噛み合っていないような印象です。 すみません。だとすれば私の前提の説明不足だと思います。実際なぜこういう現象に直面したかという経緯を1から書くとすごく長く複雑になってしまうので色々前提を端折ってしまってる部分があると思います。 > ツリーの不具合は頭痛いです。小さなツリーならダンプすればどうにかなりますが、巨大なツリーだとどうにも。時間がかかったこと自体は致し方ないのでは? やっぱりそうですか。ありがちなバグですが起きた現象の深刻度が高かったのでなんとかしたいなと思う一方で特効薬はないかんじですかね。 このバグの怖かったところはノード削除のアルゴリズムは末端ノードには子が存在するはずないという想定で, 末端ノードへの参照を切れば末端ノードはGCに行くだろうという思っていた結果,条件考慮がお粗末で中間ノードで子の参照を削除されることがあり,親の参照は消すことがないので,子から遡ると消えたはずのノードがゾンビ化して存在しrootまで参照できてしまうことで木構造が正しく存在しているように見えてしまうことと,親から遡ると子の参照は削除されてるか新しいノードが接続されているので,想定していない末端にたどり着いてしまうことが起きました(子は実装はdictionaryですが実際はインデクサのようにindex管理されてます)。 割とこういうことが起きたのが衝撃的だったので, 後学のために意見いただけてよかったです。ありがとうございます。
haru666

2017/02/14 02:54

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

2017/02/14 04:13

やっと理解できた気がします。 C/C++は不正メモリにアクセスした時、規格上は未定義ですが落としてくれる場合が多いです。参照を消し忘れると落ちるわけです。運良くバグを見つけやすいということかも知れません。(これに頼るといつかひどい目に会いますが。) それに対してC#は参照を消し忘れても絶対に落ちませんね。 参照の消し忘れは本来あってはならないことです。でもバグはいつでも忍び込みます。C/C++は運がよければ落ちてくれるけど、C#は絶対落ちないので参照消し忘れバグの検出に苦しんでいるということですね。 なるほど。確かに頭の痛い問題です。 参照を全て消すという消極的な削除ではなく、インスタンスを明示的に削除とマークして、そこへアクセスしたら落ちるような機能があればよいですね。 言語仕様上C#はそのような機能をサポートしていなので、プログラマ側で作るしかないです。ノード・クラスにそのような仕掛けを仕込むのがベストかも知れません。
退会済みユーザー

退会済みユーザー

2017/02/15 17:38 編集

haru666さん > 循環参照はしばしば問題を引き起こすので、可能な限り子供に親の参照を持たせないようにしますね。 例えば、親で子供を監視するようにします。また下記ルールを守ります。 1.特殊な親子関係は汎用コレクションをラップして親子関係専用のコレクションにする 2.コレクションの操作が複雑な場合、ラップクラスにメソッドとして設ける 3.ユニットテストを書く(GC含む) 循環参照って怖いですね。やらないほうがいいのは知っていましたが今回は身に沁みました。ただし親を参照する必要はあるのでテストで確認するのが現実的ですね。(今回も最小構成ケースでテスト書きました) > C#はきっちりとしたことを1つずつやっていくこと前提の言語という印象です。 こちら凄くよくわかります。そもそも仕様上ハマるポイントがいくつもあるのはそういう実装は推奨されないということなのでしょうね。(やろうと思えばunsafeコードでポインタ使えますが・・・。) Chironianさんがおっしゃっていることもとても参考になりました。確かに落ちる仕組みがあれば参照バグに気づけますね。 ただし、自分で作らなければいけないレベルになってしまうということは前おっしゃっていたようにオブジェクト指向的に(もしくはC#での実装)との相性が悪いのかもしれませんね。 慣れると凄く書きやすい反面、コードやメモリ参照の仕組みを理解していないと罠にはまりやすい言語だと思います。最近まではもう長いことC#触ってるので、テクニカルなコードは書けなくてもやってはいけないコードを書かない自負はあったのですが今回の件はいい薬になりました。 ご意見ありがとうございました。
guest

0

ベストアンサー

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

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

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

投稿2017/02/13 12:05

hmmm

総合スコア818

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/02/14 01:47

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

0

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

投稿2017/02/13 06:30

mituha

総合スコア385

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/02/13 06:49

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

0

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

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

(msdn).NET Framework 4.6 および 4.5

投稿2017/02/13 05:33

Y.H.

総合スコア7914

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/02/13 06:47

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

2017/02/13 06:52

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

退会済みユーザー

2017/02/13 06:58

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

0

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

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

投稿2017/02/13 04:58

haru666

総合スコア1591

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/02/13 06:47

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

0

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

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

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

投稿2017/02/13 03:00

kiichi54321

総合スコア1984

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/02/13 06:54 編集

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

2017/02/13 08:09

弱参照自体は、どうしてもこういう実装しないとダメ、というものでしかやらないほうがいいですね。 設計で回避できるものがほとんどだと思います。 そういえば、デバッグ環境としては、テストプロジェクトは便利ですよ。 http://qiita.com/kiichi54321/items/33238ec7e8a5c95b5026 
退会済みユーザー

退会済みユーザー

2017/02/13 10:47

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問