トップ回答者
ステップ実行と一括実行での値が異なる

質問
-
以下のソースでデバックモードで、ステップ実行と一括実行を行った場合、設定されている値が意図としない値で設定されることが
あります。
環境 windows vista(32bit) メモリ2G Microsoft Visual Basic 2008 Express Edition
ソース
- 1.Dim MaxSplitCnt As Integer = 0
- Dim CompHd As New CommonClassLibrary1.CommonOpeNum
- Array1Cnt = CompHd.GetRoundUp(pOutCnt / MaxSplitCnt) ’GetRoundUpはintger型
- For idx1 = 0 To Array1Cnt - 1
- Dim AccHd As New CommonClassLibrary1.FileAcc
- AccHd.SetRecords = pRecords(idx1) 'pRecordsは、privateで定義されている領域
- ・(省略)
- AccHd.Writes(x、x、x)
- AccHd = Nothing
- System.GC.Collect()
- Next
上記コードで一括実行(デバックモードで<続行ボタンクリック>)した場合、エラーで異常終了しました。
調査すると、4行目Array1Cntの値が6となって添え字エラーとなってました。このとき、ウォッチで3行目の CompHd.GetRoundUp(pOutCnt / MaxSplitCnt)の値が3です。(pOutCnt =705 MaxSplitCnt=250)
CompHd.GetRoundUpは、小数点以下切り上げをするクラスであり、結果3として正しい値を取得しています。それにも関らず値の複写先であるArray1Cnt とは異なった値となっています。なお5行目~10行目にはArray1Cnt を参照してません。(for判定文のみで使用)
上記現象は、4行目で一時停止してステップ実行すると再現しません。(MaxSplitCntには必ず3が入る)また、必ず起きるのではなく連続して実行すると当現象が発生する場合があります。
何故このような結果となるか皆目見当がつかない状態であります。どなたか解る方いらっしゃいましたご回答をよろしくお願いします。
回答
-
解決しました。
>スタック絡みが怪しい気がします。MaxSplitCnt とか Array1Cnt の Dim を代入する直前に移動するとどうなるでしょうか?また、pOutCnt と MaxSplitCnt のどちらか一方ずつを定数にした場合に動作はどうなるでしょうか?また、問題の行の前後で pOutCnt、MaxSplitCnt、Array1Cnt それぞれの値をトレース出力するとどうなるでしょうか?
MaxSplitCnt とか Array1Cnt の Dim を代入する直前に移動するとし、今まで、ブレークポイントにてpOutCntが、703と見えてたものが1406と見えるようになりました。その後、dimの位 置を元の位置にもどしても、703とは見えずに1406と見えるようになりました。あとは1406となるための原因を修正した結果、正常に動作するようになりました。どうも、ウォッチが誤動作してたようです。 (1406となってたのは、単純なこちらのバグです。)
上記、表は確かに逆でした。ご指摘ありがとうございます。
>トレース出力について
expressには、トレース機能ってないんですよ。
みなさん、ほんとうにありがとうございました。ほんと助かりました!
- 回答としてマーク 相良宗助軍曹 2011年4月11日 4:41
すべての返信
-
>条件付きのブレークポイントでも同様なのですか?
ExpressEditionには、ないようです。>これは必ずしもそうとは言い切れないと思います。インスタンスがどうかという話だと思いますので。(プロパティなどを介して値を変更できるとして。)
当ソースは、とあるクラスのあるメンバーのソースです。また、MaxSplitCnt は、行1番で定義して、行3番で使用しているだけです。MaxSplitCnt はプロパティを介して値の変更も行っていません。純粋に記述しているソースのみの情報だけです。もう少し細かく説明しますと、3行目でブレークポイントを指定し、ウォッチしてみるとCompHd.GetRoundUp(pOutCnt / MaxSplitCnt)が「3」 1行ステップオーバーでArray1Cntの値は「3」でした。 -->正常
4行目でブレークポイントを指定し、ウォッチしてみるとCompHd.GetRoundUp(pOutCnt / MaxSplitCnt)が「3」でArray1Cntは「6」でした。 -->異常?? という感じです。
-
もう少し細かく説明しますと、3行目でブレークポイントを指定し、ウォッチしてみるとCompHd.GetRoundUp(pOutCnt / MaxSplitCnt)が「3」 1行ステップオーバーでArray1Cntの値は「3」でした。 -->正常
4行目でブレークポイントを指定し、ウォッチしてみるとCompHd.GetRoundUp(pOutCnt / MaxSplitCnt)が「3」でArray1Cntは「6」でした。 -->異常?? という感じです。
この症状だけみると、外部からの処理がないかぎり、CompHd.GetRoundUp が呼び出す毎に違う結果を返しているということになります。
4行目でブレークポイントをしかけてウォッチた場合、CompHd.GetRoundUp は2回呼び出されます。1回目の呼び出し結果は、Array1Cnt に格納された値であり、2回目に呼び出された結果がウォッチしている値となります。この値が異なるということは、
- CompHd.GetRoundUp に与えている値が変化している。(入力が違うから出力が違う)
- CompHd.GetRoundUp が与えた値を切り上げるだけの機能ではない。(使い方が間違っている)
- CompHd.GetRoundUp の不具合。(COM 呼び出し規約の設定間違いなどで浮動小数点値がきちんと渡らないとか、スタックの破壊も含む)
といった原因が考えられます。書き換えられている可能性があるのは Array1Cnt だけでなく、pOutCnt や MaxSplitCnt もです。これらが外部で変化すると、ウォッチ式として表示している数字は当然異なります。
とりあえず、それなりの頻度で再現することができているなら、入力がわるいのか出力がわるいのかを切り分けるために、CompHd.GetRoundUp のかわりに、Math.Ceiling を使用してみてはいかがでしょうか? Math.Ceiling は .NET の標準ライブラリに含まれる切り上げ処理です。Math.Ceiling に置換して症状が改善しない場合、外部で Array1Cnt か pOutCnt が変更されている可能性が高いのではないかと思います。
# 脳が C# だと、 int/int を切り上げできるのが変な気になりますね^^ VB は int/int = double なのでこれで問題ないのですが…。
-
>4行目でブレークポイントをしかけてウォッチた場合、CompHd.GetRoundUp は2回呼び出されます。1回目の呼び出し結果は、Array1Cnt に格納された値であり、2回目に呼び出された結果がウォッチしている値となります。この値が異なるということは
CompHd.GetRoundUp(pOutCnt / MaxSplitCnt) この値は、常に正しい値です。pOutCntもMaxSplitCntも前記述した2つのパターンについて確認しましたが同じ値であることは確認しています。ちなみに、Cint(Math.Ceiling( pOutCnt / MaxSplitCnt))に置き換えても結果は同じでした。
上記以外の確認として、For idx1 = 0 To Array1Cnt - 1の「Array1Cnt - 1」を「pRecords.length -1」とすると正しく処理されます。つまり、上記テストパターンにてArrat1Cntには常に「3」が入ります。
>とりあえず、それなりの頻度....外部で Array1Cnt か pOutCnt が変更されている可能性が高いのではないかと思います。
Arrat1Cnt =0 とはいっているものに対して、3を入れているだけの処理なのに、Arra1Cntの内容が6になっているというのが不思議でならない。また、「外部で Array1Cnt か pOutCnt(private) が変更されている可能性」というのが意味不明 すくなくともArray1Cntは、ローカル定義(メソッド内のみ有効変数)でありステップ的にも1行しか進んでいない且つブレークポイントの位置を1行下にしただけで、外部から変更されるというのはどう考えても、あり得ないと思います。これで考えられるのはビルドのバグを疑ったほうがよいと思うのですが、如何でしょうか? ビルドのバグだと家庭してそれはどのように対処すればいいのかは疑問ですが。(何しろ無料のツールを使っている身分なので)
#Math.Ceiling なんてあるなんて知りませんでした。切り上げ処理について結構探したのですが、見つけれなくて結局自分で切り上げ処理を共通プロジェクトのの数値オペレーションクラス(CommonOpeNum)のメソッドとして追加してしまった。
-
当ソースは、とあるクラスのあるメンバーのソースです。また、MaxSplitCnt は、行1番で定義して、行3番で使用しているだけです。MaxSplitCnt はプロパティを介して値の変更も行っていません。純粋に記述しているソースのみの情報だけです
そのようなことはないはずです。
その証拠に正しく動くケースでは MaxSplitCnt が 250 になっているのですよね?
少なくとも 0 除算が発生しないということは、使用するまでに何らかの値を入れているのですよね?
pOutCnt と MaxSplitCnt を変更する部分をすべて洗い出して、Trace 出力を入れて値を監視してはいかがでしょうか? -
>pOutCnt と MaxSplitCnt を変更する部分をすべて洗い出して、Trace 出力を入れて値を監視してはいかがでしょうか?
pOutCnt と MaxSplitCntの部分は、いかなる場合も正しい値です。問題なのは、Array1Cnt の値です。また、当質問の内容は3行目までに、pOutCnt と MaxSplitCntが変更されることが問題ではないのです。あくまでも3行目から4行目の間で、CompHd.GetRoundUp(pOutCnt / MaxSplitCnt) で得た結果と、Array1Cnt の結果が等しくないことが問題なのです。なお、異常正常を問わず、pOutCnt とMaxSplitCntの値が常に同じ値であることは、確認済みです。
それに加え、4行目で、CompHd.GetRoundUp(pOutCnt / MaxSplitCnt)ウォッチしたときの結果は、CompHd.GetRoundUp(pOutCnt / MaxSplitCnt)が2回走っていることになるので、3行目でCompHd.GetRoundUp(pOutCnt / MaxSplitCnt)をウォッチしましたが結果は同じく、3であることも確認しています。
-
pOutCnt と MaxSplitCntの部分は、いかなる場合も正しい値です。問題なのは、Array1Cnt の値です。また、当質問の内容は3行目までに、pOutCnt と MaxSplitCntが変更されることが問題ではないのです。あくまでも3行目から4行目の間で、CompHd.GetRoundUp(pOutCnt / MaxSplitCnt) で得た結果と、Array1Cnt の結果が等しくないことが問題なのです。なお、異常正常を問わず、pOutCnt とMaxSplitCntの値が常に同じ値であることは、確認済みです。
それに加え、4行目で、CompHd.GetRoundUp(pOutCnt / MaxSplitCnt)ウォッチしたときの結果は、CompHd.GetRoundUp(pOutCnt / MaxSplitCnt)が2回走っていることになるので、3行目でCompHd.GetRoundUp(pOutCnt / MaxSplitCnt)をウォッチしましたが結果は同じく、3であることも確認しています。
Array1Cnt という変数を他で使っていて、値を変更してしまう可能性はないのでしょうか?
このような現象の場合、「絶対に正しい」と決め付けるのでなく、あらゆる可能性を想定して地道につぶしていくのが結果的に早く解決できるように思います。 -
>Array1Cnt の値が設定から参照までの間に変化してしまったということになります
設定から参照までと言われても、設定が3行目、参照が4行目です。。
別観点で、質問ソース抜粋 3,4行目でpOutCntは703固定で MaxSplitCntを変化させてArrat1Cntがどのように変化するか試験してみました。 ブレークポイントは FORの行です。
Array1Cnt = CInt(Math.Ceiling(pOutCnt / MaxSplitCnt)) ’CompHd.GetRoundUp(pOutCnt / MaxSplitCnt) と同じ結果なので
For idx1 = 0 To Array1Cnt - 1 <-ブレークし、以下の内容をウォッチpOutCnt MaxSplitCnt CInt(Math.Ceiling(pOutCnt / MaxSplitCnt)) Array1Cnt
703 250 3 6
703 176 4 8
703 173 5 9
703 118 6 12
703 111 7 13
と変化します。
- 編集済み 相良宗助軍曹 2011年4月11日 3:39
-
>Array1Cnt の値が設定から参照までの間に変化してしまったということになります
設定から参照までと言われても、設定が3行目、参照が4行目です。。
もちろん、ご提示のソース コード上ではその通りです。
ただ、実際に Array1Cnt の値が設定時と参照時で異なっているとのことですので、設定から参照までに変化したというのが論理的な説明だと思います。
第三者から見た場合、いくつかの可能性が考えられます。
・(トレース実行の有無の差として一番ありがちな)スレッドの実行タイミングによって、値がよそで書き換えられてしまう。
・Array1Cnt が実はプロパティで、中で何らかの処理をしている。
・For ループの中で Array1Cnt の値を変更している。
.....
Array1Cnt をローカルのスコープで Dim してみるとか、違う変数名にしてみるとか、(無駄とは思っても)Array1Cnt の値をトレース出力してみるとか、これらはあくまでも第三者からみた視点です。 -
すいません、最初のソースサンプルの書き方が悪かったみたいで、状況が今一伝わらなかったように思います。再度以下にソースを編集したものを記述します。
- Private pOutCnt As Integer = 0
- Private pRecords()() As String
- public sub xxxx()
- Dim MaxSplitCnt As Integer = 0
- Dim Array1Cnt As Integer = 0
- SplitData() <-ここのサブで、pOutCnt およびpRecordsが設定される。
- Dim CompHd As New CommonClassLibrary1.CommonOpeNum
- MaxSplitCnt = pOutParameterClassHd.GetPostOutSplitRecordCnt
- Array1Cnt = CompHd.GetRoundUp(pOutCnt / MaxSplitCnt) ’GetRoundUpはintger型
- For idx1 = 0 To Array1Cnt - 1
- Dim AccHd As New CommonClassLibrary1.FileAcc
- AccHd.SetRecords = pRecords(idx1) 'pRecordsは、privateで定義されている領域
- ・(省略)
- AccHd.Writes(x、x、x)
- AccHd = Nothing
- System.GC.Collect()
- Next
- end sub
- Private Sub SplitData()
- ..pOutCnt およびpRecordsの設定
- end sub
> ・(トレース実行の有無の差として一番ありがちな)スレッドの実行タイミングによって、値がよそで書き換えられてしまう。
Array1Cnt をArray1Cnt 1に名前変更しても同じ結果となりました。(変更したときも、forの中に対象項目がないことも確認しています。)またArray1Cnt を検索かけても当サブルーチン内で使用しているのは5,9, 10行目のみであることを確認しています。
> ・Array1Cnt が実はプロパティで、中で何らかの処理をしている。
> ・For ループの中で Array1Cnt の値を変更している。上記ソースから、Arrat1Cntがもともと、ローカルで定義されていることが解ると思いますので上記疑問点が対象外であると考えられると思います。
-
(意図的じゃない場合はわかりにくいかもしれませんが)複数のスレッドは使用されているのでしょうか?
前回書いてますが、正常に動いてる前提だと、pOutCnt が外部から書き換えられている可能性が懸念されますね。あとは、これも前回書いてますが CompHd や AccHd、ループ内の各処理において P/Invoke や COM 相互呼び出し等を使用されているならば、定義ミスでスタックの破壊をしてしまっている可能性が一番高そうに思います。
(デバッグシンボルの不一致という可能性もありますが、シンボルの不一致はデバッガ上の見た目はくるっても、実行結果はおかしくならないので除外)
-
>(意図的じゃない場合はわかりにくいかもしれませんが)複数のスレッドは使用されているのでしょうか?
前にも書いてますが、スレッドは使ってません。もし、この状態で変更されるのでしたらOSレベルで悪さしている(メモリ破壊)を起こしているとしか思えないです。
>CompHd や AccHd、ループ内の各処理において P/Invoke や COM 相互呼び出し等
P/Invoke も使用してません。COM 相互呼び出しについては、エラーになると思いますよ、確かこの処理についてではないのですがプロジェクトの参照設定で誤って設定しようとしてコンパイラーに怒られた記憶があります。
>(デバッグシンボルの不一致という可能性もありますが、シンボルの不一致はデバッガ上の見た目はくるっても、実行結果はおかしくならないので除外)
これは気になりますね! もしくるっていると過程してどのような確認をすればいいのでしょうか?具体的には、Arrat1Cntが意図としない値に見えているが実は別の項目(別のアドレス)が参照されていて、その後別のアドレスにて制御されてるってことでしたら確認のしようがないと思うのですが。意図としない値とは、前回2つ前の回答で5つの試験パターンを記述していますが、その中で最初のパターンを除きCInt(Math.Ceiling(pOutCnt / MaxSplitCnt)) の値が偶数の場合、Array1Cntの値が2倍となり奇数の場合2倍-1の値となっています。これも気になるところです。
pOutCnt MaxSplitCnt CInt(Math.Ceiling(pOutCnt / MaxSplitCnt)) Array1Cnt
703 250 3 6 <- このパターンを除き
703 176 4 8 <ーCInt(Math.Ceiling(pOutCnt / MaxSplitCnt)) が偶数
703 173 5 9
703 118 6 12 <ーCInt(Math.Ceiling(pOutCnt / MaxSplitCnt)) が偶数
703 111 7 13
ちなみに、CInt(Math.Ceiling(pOutCnt / MaxSplitCnt)) のpOutCnt とMaxSplitCntを定数で固定的に指定すると上記状態は再現しません。これは、Ceilingの部分をCompHd.GetRoundUp(pOutCnt / MaxSplitCnt) にしても同じように再現しません。
- 編集済み 相良宗助軍曹 2011年4月11日 3:39
-
>(デバッグシンボルの不一致という可能性もありますが、シンボルの不一致はデバッガ上の見た目はくるっても、実行結果はおかしくならないので除外)
これは気になりますね! もしくるっていると過程してどのような確認をすればいいのでしょうか?具体的には、Arrat1Cntが意図としない値に見えているが実は別の項目(別のアドレス)が参照されていて、その後別のアドレスにて制御されてるってことでしたら確認のしようがないと思うのですが。
デバッグ シンボルが不一致の場合は、デバッガーが見当違いな値を表示するだけで、実際のプログラム実行には全く影響しません。K.Takaoka さんの見解通りに除外しても問題ないと思います。(デバッガーとか関係なく、プログラムを普通に実行しても、インデックスの範囲外例外は発生するのですよね?)
ちなみに、CInt(Math.Ceiling(pOutCnt / MaxSplitCnt)) のpOutCnt とMaxSplitCntを定数で固定的に指定すると上記状態は再現しません。これは、Ceilingの部分をCompHd.GetRoundUp(pOutCnt / MaxSplitCnt) にしても同じように再現しません。
スタック絡みが怪しい気がします。MaxSplitCnt とか Array1Cnt の Dim を代入する直前に移動するとどうなるでしょうか?また、pOutCnt と MaxSplitCnt のどちらか一方ずつを定数にした場合に動作はどうなるでしょうか?また、問題の行の前後で pOutCnt、MaxSplitCnt、Array1Cnt それぞれの値をトレース出力するとどうなるでしょうか?
ところで、
pOutCnt MaxSplitCnt CInt(Math.Ceiling(pOutCnt / MaxSplitCnt)) Array1Cnt
703 250 3 6 <- このパターンを除き
703 173 4 8 <ーCInt(Math.Ceiling(pOutCnt / MaxSplitCnt)) が偶数
703 176 5 9
703 118 6 12 <ーCInt(Math.Ceiling(pOutCnt / MaxSplitCnt)) が偶数
703 111 7 13
太字の部分は値が逆と考えていいですか?期待値と合わなくなるので。 -
解決しました。
>スタック絡みが怪しい気がします。MaxSplitCnt とか Array1Cnt の Dim を代入する直前に移動するとどうなるでしょうか?また、pOutCnt と MaxSplitCnt のどちらか一方ずつを定数にした場合に動作はどうなるでしょうか?また、問題の行の前後で pOutCnt、MaxSplitCnt、Array1Cnt それぞれの値をトレース出力するとどうなるでしょうか?
MaxSplitCnt とか Array1Cnt の Dim を代入する直前に移動するとし、今まで、ブレークポイントにてpOutCntが、703と見えてたものが1406と見えるようになりました。その後、dimの位 置を元の位置にもどしても、703とは見えずに1406と見えるようになりました。あとは1406となるための原因を修正した結果、正常に動作するようになりました。どうも、ウォッチが誤動作してたようです。 (1406となってたのは、単純なこちらのバグです。)
上記、表は確かに逆でした。ご指摘ありがとうございます。
>トレース出力について
expressには、トレース機能ってないんですよ。
みなさん、ほんとうにありがとうございました。ほんと助かりました!
- 回答としてマーク 相良宗助軍曹 2011年4月11日 4:41
-
>トレース出力について
expressには、トレース機能ってないんですよ。
さすがにトレース出力機能はあります。VB 2008 Express Edition でも動作します。
@IT:連載:プロフェッショナルVB.NETプログラミング 第13回 デバッグとトレース
http://www.atmarkit.co.jp/fdotnet/vb6tonet/vb6tonet13/vb6tonet13_02.html
VB には昔ながらの Debug.Print 機能もあり、これも Express Edition でも動作します。 -