リストコントロールの列数変更によるエラー?

解決


なちゅら  2009-11-11 22:31:00  No: 71081

開発環境はVC++6.0(SP6適用)、MFCダイアログベース(MFC AppWizard使用)です。

リストコントロール(m_list_result)の列追加をOnInitDialogで
static char* lpTitel[3] = { "1" , "2", "3"};
static int iWidth[2] ;
for ( int i = 0 ; i < 3 ; i++ ){
  switch (i) {
  case 0:
    iWidth[i] = m_list_result.GetStringWidth(lpTitel[i]) * 2;
    m_list_result.InsertColumn( i, lpTitel[i], LVCFMT_LEFT, iWidth[i], -1 );
    break;
  …以下同様にcase1、case2が続く…
  }
}
としていたのを
static char* lpTitel[4] = { "1" , "2", "3", "4"};
static int iWidth[2] ;
for ( int i = 0 ; i < 4 ; i++ ){
  switch (i) {
  case 0:
    iWidth[i] = m_list_result.GetStringWidth(lpTitel[i]) * 2;
    m_list_result.InsertColumn( i, lpTitel[i], LVCFMT_LEFT, iWidth[i], -1 );
    break;
  …以下同様にcase1、case2、case3が続く…
  }
という風に列を1つ増やしてコンパイル・実行した所、
実行画面が出る前に
「ハンドルされていない例外はXXX.exeにあります。0xC0000005:Access Violation。」
と表示され、OKを押すとAFXWIN1.INLの
_AFXWIN_INLINE CWnd* AFXAPI AfxGetMainWnd()
    { CWinThread* pThread = AfxGetThread();
        return pThread != NULL ? pThread->GetMainWnd() : NULL; }
という部分の最後、returnがある行を指しています。

元に戻すときちんと動作するため、列数を増やしたことに原因があるのでは
ないかと思うのですが、何がいけないのでしょう?
よろしくお願い致します。


tetrapod  2009-11-11 22:36:33  No: 71082

最初のコードがうまく動いていたのは偶然としか思えない。
なぜ iWidth[2] なの? [3] や [4] ぢゃないの?
# case 0 以後が省略されているので真相はわからん


なちゅら  2009-11-11 23:16:40  No: 71083

iWidth[2]なのは参考元のままだったからです。
確かに[3]や[4]の方が良いですね…

case0以後は、iWidth[i] =…の文だけが少し変わります。
case1
iWidth[i] = m_list_result.GetStringWidth(lpTitel[i]) * 3;
case2
iWidth[i] = m_list_result.GetStringWidth(lpTitel[i]) * 2;
case3
iWidth[i] = m_list_result.GetStringWidth(lpTitel[i]) * 3;

iWidth[3]にしてみると動きました。
しかし、[2]に戻しても動きます。
根本的に解決できたと言っていいか怪しいです…


tetrapod  2009-11-12 01:31:43  No: 71084

int iWidth[2]; なる変数定義に対して iWidth[2] へ書き込んでいるのは明らかにバグなので修正する必要がある。

C/C++ では「間違っていても一見正しく動いているように見える」ことはごく普通にある。

・バグっているコードが、正しく動いているように見える
・バグっていないコードが正しく動いている
この両者は「見た目」同じ動きをしているように思えるわけだが、
「動いているように見える」からといって修正しなくて良いわけではない、よな。

> 根本的に解決できたと言っていいか怪しいです…
それは俺たち読者の知ったことではなくてご自分で解決するべき問題だと思うぞ。

バグっているコードがどこのメモリをどう破壊しているか機械語レベルで理解し、
例えば未使用な領域を壊しているから結果的に問題なかったことを自分で保証するもよし。
例えば他の変数を壊しているけど・・・だから問題なかったことを以下略
例えば***で***だから***で・・・以下略

修正せずただ動いているように見えるからそれでよし、と見て見ぬ振りをするもよし。

正しく直し、正しいプログラムが問題なく動作しているのでOKとするもよし。


maru  2009-11-12 01:42:25  No: 71085

> iWidth[2]なのは参考元のままだったからです。
> 確かに[3]や[4]の方が良いですね…
iWidthがどういう目的の変数かを考えれば、要素数をいくつにすべきかは
自明だと思うが。ソースをコピペしてその時動いたとしても内容を理解し
ないといつまでもこういう状況が続くよ。

> しかし、[2]に戻しても動きます。
iWidth[2]の次の領域がたまたま使用されていなかったか、正しい値になって
いなくてもエラーとなって表に出てこなかったため。
あくまでたまたま動いているだけで、これはバグです。

こういうバグは別の場所に影響が出てくるので、デバッグが厄介です。
配列のサイズには気をつけましょう。

> case0以後は、iWidth[i] =…の文だけが少し変わります。
> case1
> iWidth[i] = m_list_result.GetStringWidth(lpTitel[i]) * 3;
> case2
> iWidth[i] = m_list_result.GetStringWidth(lpTitel[i]) * 2;
> case3
> iWidth[i] = m_list_result.GetStringWidth(lpTitel[i]) * 3;
コピペに慣れてしまっているからこういうコードを書いちゃうんだろうね。
for ( int i = 0 ; i < 3 ; i++ ){
  int width = 3;
  switch (i) {
  case 2: width = 2;    break;
// 以下係数が3以外のところだけcase文を書く
  }
    iWidth[i] = m_list_result.GetStringWidth(lpTitel[i]) * width;
    m_list_result.InsertColumn(i, lpTitel[i], LVCFMT_LEFT, iWidth[i], -1 );
}
同じコードはなるべく書かないようにしないと。


maru  2009-11-12 01:46:04  No: 71086

かぶった。orz


なちゅら  2009-11-12 22:59:42  No: 71087

tetrapodさん、maruさん、ありがとうございました。

とりあえずこの記事は解決にしておきます。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加