• ベストアンサー

コンパイラによってエラーが出たり出なかったり

構造体をポインタでつなぐリスト構造を使ったソースを書いて、Borland C++ Compiler 5.5 でコンパイルしたものを実行するとエラーで止まってしまいました。 コンパイル時には何も警告が出ませんでした。 しかし、ソースを何度見てもバグらしいコードが見つからなかったので、試しに Microsoft .NET Framework SDK でコンパイルしてみたところ、こちらも警告も出ずにコンパイルが通って、実行してみたらこんどは正常に動作しました。 コンパイラによって、実行時にエラーが出たり出なかったりするということは初めてなのでその原因を知りたくて質問しました。 どういった場合にこのようなことが起こるのでしょうか。 Borland C++ で出たエラーは、おそらくセグメントエラーだと思います。 今のところ正常に動いているのですが、なにかデンジャラスなコードを書いてしまった気がして気持ち悪いです。

質問者が選んだベストアンサー

  • ベストアンサー
  • tatsu99
  • ベストアンサー率52% (391/751)
回答No.10

curの値が破壊される原因ですが、 char buf[80], n, i, b; を char buf[80]; int n,i,b; にして下さい。 それでうまく動くようになります。 fscanf(fp,"%d ", &n) の場合、%dの為、nはint型であると、fscanfは解釈します。 その為に、4バイトのデータをnにセットしますが、nがchar型の為、nの後ろの3バイトが破壊されます。 bも同じ理由で、bの後ろ3バイトが破壊されます。 これが、curを破壊していると、考えられます。 Microsoft .NET Framework SDK は、たまたま、curを破壊していないだけと考えられます。

ondy
質問者

お礼

おかげさまで、全てすっきり解決いたしました。 char 型の変数に対して %d で読み込んだ場合、メモリに配置される前に1バイトに丸められてメモリに書き込まれるものだと思い込んでいました。はみ出た分は他の領域に上書きされるのですね。 n や b の直後に cur が割り当てられていたためにはみ出た分が cur に上書きされるので、エラーが出なかったのは cur とn, b がたまたま離れた領域に配置されたからですね。 小さい整数なら、int 型よりもメモリ使用量が少ない char 型がよく使われるとどこかに書いてあって、今までたった数バイトをケチるためにこんな書き方をしていました。 思い返すと、過去に出したエラーでそのとき結局原因が特定できなかったのも char 型に %d で数値を読み込んでいたような気がします。 何がいけないのかはっきり分かったので、もうこんなバグは出しません。未熟な私のこんな質問に付き合っていただいてありがとうございました。 感謝しきれないほど感謝しています!

その他の回答 (9)

  • tatsu99
  • ベストアンサー率52% (391/751)
回答No.9

ソース提示ありがとうございました。 double radius(char n, double *keisuu, double max, double min); のこの関数のソースがないので、提示していただけませんでしょうか。 とりあえず、 cur->r=radius(n, keisuu, max, min);の行を cur->r=1.0;として実行しました。 これで、エラーがおこりました。 cur=&start;の時点では正しい値がcurに入っていますが、 do{ cur->r=radius(n, keisuu, max, min); の時点で、curの値が破壊されています。 したがって、 cur=&start;以降から、 do{ cur->r=radius(n, keisuu, max, min); に入るまでに、curの内容をどこかでこわしています。 試しに、cur=&start;を do{の直前に持っていくと、うまく動作します。 どこで、curを壊すかについては、これから調査しますので、もうすこし おまちください。 尚、Microsoft .NET Framework SDK でうまく、動くのは、メモリの配置が borandと異なる為に、たまたま、curの箇所は、こわされないでいたためと考えられます。 以上、中間報告でした。

ondy
質問者

お礼

確かに、cur = &start; を do ループの直前に移動したらうまく動作しました。 結局、リスト構造以前の問題でした。ソースを見ていただいて良かったです。 お忙しいなか質問に付き合っていただいて本当に感謝しております。

  • tatsu99
  • ベストアンサー率52% (391/751)
回答No.8

問題の関数以外のmainの全て及びグローバル領域も提示していただけませんでしょうか。(要はソースの全てです)こちらでも、Borland C++ Compiler 5.5でコンパイル&実行してみますので。

ondy
質問者

補足

お手数かけてすみません。 それでは、全て提示いたします。 今のところ作業に支障があるわけでもなく、特に修正を急いでいるわけではないのでお時間があるときに見ていただければと思います。 これが何をやっているプログラムなのかということはあまり突っ込まないでください。 入力ファイルが3つあります 粒径分布.txt (ここから) データ個数 8 直径[mm] 割合[%] 0.150 0.6 0.180 5.0 0.212 9.3 0.250 10.3 0.300 16.5 0.355 26.2 0.425 25.2 0.500 5.3 (ここまで) 補間多項式.txt (ここから) 7 4229.770587 -113974.061186 1270436.276810 -7600248.405033 26404288.434586 -53356880.425991 58190792.114114 -26484748.697527 (ここまで) setup2.txt (ここから) 半径[mm] 3.65 高さ[mm] 5.0 目標空隙率[%] 36.0 精度[mm] 0.01 (ここまで) 以下がソースです。長いので、途中で区切って#5の補足欄に続きを書きます。 #include <stdio.h> #include <math.h> #include <stdlib.h> #include <time.h> #define PI 3.141592 int STEP; double DIFFERENCE; struct model{ double x; double y; double z; double r; struct model *next; }start; double radius(char n, double *keisuu, double max, double min); char set(struct model *cur, double Lr, double Lz); int main(void) { double Lr, Lz, porosity, bvol, pvol=0.0, max, min, curp, y=0.0, ave=0.0, rmin=10.0, rmax=0.0, *keisuu; struct model *cur, *comp; FILE *fp; char buf[80], n, i, b; int counter=0; srand((unsigned)time(NULL)); if((fp=fopen("setup2.txt","r"))==NULL){ printf("setup2.txtを開く際にエラー発生\n"); exit(1); } fscanf(fp,"%s %lf ", buf, &Lr); fscanf(fp,"%s %lf ", buf, &Lz); fscanf(fp,"%s %lf ", buf, &porosity); fscanf(fp,"%s %lf ", buf, &DIFFERENCE); bvol=Lr*Lr*PI*Lz; cur=&start; fclose(fp); STEP=(int)Lz/DIFFERENCE*2.0+1; if((fp=fopen("補間多項式.txt","r"))==NULL){ printf("補間多項式.txt を開く際にエラー発生\n"); exit(1); } fscanf(fp,"%d ", &n); keisuu=(double *)malloc(sizeof(double)*(n+1)); if(!keisuu){ printf("メモリ割り当てエラー\n"); exit(1); } for(i=0;i<n+1;i++) fscanf(fp,"%lf ", &keisuu[i]); fclose(fp); if((fp=fopen("粒径分布.txt","r"))==NULL){ printf("粒径分布.txtを開く際にエラー発生\n"); exit(1); } fscanf(fp,"%s %d ", buf, &b); fgets(buf, 80, fp); fscanf(fp,"%lf %lf ", &min, &max); for(i=0;i<b-1;i++) fscanf(fp,"%lf %lf ", &max, &curp); fclose(fp); do{ cur->r=radius(n, keisuu, max, min); if(set(cur, Lr, Lz)) break; pvol+=4.0*PI*pow(cur->r, 3)/3.0; if(y < cur->z + cur->r) y = cur->z + cur->r; if(rmin > cur->r) rmin = cur->r; if(rmax < cur->r) rmax = cur->r; printf("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b空隙率:%.2lf [%] 現在の高さ:%.2lf [mm] 粒子数:%d 個", curp=100.0*(1-pvol/bvol), y, counter); counter++; ave+=cur->r; cur->next=(struct model *)malloc(sizeof(struct model)); if(!cur->next){ printf("メモリ割り当てエラー\n"); exit(1); } cur=cur->next; }while(curp>porosity); printf("\n"); /*#5の補足へ続きます*/

  • Interest
  • ベストアンサー率31% (207/659)
回答No.7

動作ログ収集してみては? ログ収集用の関数を用意して、ログ収集関数の中では 1.ログファイルを開く 2.書き込む 3.閉じる を実行します。 いちいちファイルを開いて閉じるのは、プログラムが落ちてもログがちゃんと残るようにするためです。 ログ収集関数を主要な関数の出入り口やエラー処理部、複雑な処理が入る前後に仕込んでおけば、どこで不具合が発生したか捕らえやすくなります。 ログ収集関数内部はプリプロセッサで無効化する仕掛けを入れておけば、(処理速度は変わりますが)ログ収集が不要になったときにすぐ外せますよね。 あとは、ちゃんとテスト項目(例えば引数、限界値、境界値、異常系、正常系)をリストアップして、テストを実施する前にテスト項目リストを片手にコードレビューをしてみることをお勧めします。

ondy
質問者

お礼

Interest さん、回答ありがとうございます。 大学の研究室で計算化学のプログラムを組んでるぐらいでそれほど高度なことをやっているわけではないので、動作ログを収集する方法は初めて知りました。 今回のケースに限らず、役立つことを教えていただきありがとうございます。 さっそくやってみます。

  • Oh-Orange
  • ベストアンサー率63% (854/1345)
回答No.6

★『問題が発生したため、….exeを終了します。ご不便をかけて申し訳ありません。』 ・上記のエラー・ダイアログから『cur = &start;』の1行が怪しいですね。 ・多分『start』は関数の引数で受け取る仮引数ですよね。それも構造体ポインタへの  ポインタでしょう。それだと『cur = *start;』の1行が正しい代入・参照だと思います。  『&』はアドレス演算子ですので『cur』には構造体ポインタへのアドレスであり、先頭  アドレスのポインタが『cur』に代入されないため、『while(cur->next){…}』で参照した  時にあのエラー・ダイアログが出ると思います。 ・もしよろしければ関数本体を丸ごと提示して下さい。 ・以上。おわり。

ondy
質問者

お礼

再びありがとうございます。説明不足ですみません。 startは、グローバル領域に置いてあるのです。 構造体は struct model{  double x;  double y;  double z;  double r;  struct model *next; }start; と定義して、 main()関数の中で struct model *cur; と宣言し、 cur = &start; と初期化してから問題の関数を  do{   cur->r = …;   if(set(cur, Lr, Lz))    break;   ……   cur->next = (struct model *)malloc(sizeof(struct model));   if(!cur->next){    printf("メモリ割り当てエラー\n");     exit(1);   }   cur=cur->next;  }while(…); と呼び出しています。 問題の関数を丸ごと提示させていただきます。 お暇なときに見ていただければと思います。 char set(struct model *cur, double Lr, double Lz) {  double m=RAND_MAX, theta, phi;  struct model pre, *comp;  long int i=0;  char f;  do{   f=0;   do{    cur->x=rand()*2*Lr/m-Lr;    cur->y=rand()*2*Lr/m-Lr;    cur->z=Lz-cur->r;   }while(pow(cur->x, 2)+pow(cur->y, 2) > pow(Lr - cur->r, 2));   comp=&start;   while(comp!=cur){    if(pow(comp->x - cur->x, 2)+pow(comp->y - cur->y, 2)+pow(comp->z - cur->z, 2) < pow(comp->r + cur->r, 2)){     f=1;     break;    }    comp=comp->next;   };   if((i++)==50000)    return 1;  }while(f);  for(i=0;i<STEP;i++){   pre=*cur;   theta=rand()*PI/(2.0*m);   phi=rand()*2.0*PI/m;   cur->x=pre.x+DIFFERENCE*sin(theta)*cos(phi);   cur->y=pre.y+DIFFERENCE*sin(theta)*sin(phi);   cur->z=pre.z-DIFFERENCE*cos(theta);   if(pow(cur->x, 2)+pow(cur->y, 2) > pow(Lr - cur->r, 2) || cur->z < cur->r)    *cur=pre;   else{    comp=&start;    while(comp!=cur){     if(pow(cur->x - comp->x, 2)+pow(cur->y - comp->y, 2)+pow(cur->z - comp->z, 2) < pow(comp->r + cur->r, 2)){      *cur=pre;      break;     }     comp=comp->next;    }   }  }  return 0; }

  • tatsu99
  • ベストアンサー率52% (391/751)
回答No.5

#3です。 >一つは、main()関数の中でdouble型のポインタに対してmalloc()関数でメモリを割り当てて、そのポインタを他の関数に渡してその関数の中で確保した領域を配列としてアクセスしているところです。 上記は、特に問題ありません。当たり前の話ですが、 double *p = malloc(100*sizeof(double)); で割り当てた時、配列は、0~99迄ですので、100以上のところへのアクセスは、してないですね。 mallocであれば、問題ありませんが、 char area[100*sizeof(double)]; とした場合は、doubleのサイズが8として、800バイトの領域がとられますが、これは、800バイトの領域を確保しているのみであり、double型で、参照、設定が行えることを保証するものではありません。この領域が奇数番地等のdouble型で参照できないアドレスから始まる可能性があります。このような使い方は、してないと思いますが、念のため。もちろん、double area[100];としていれば、double型で参照、設定が可能です。 2つ目の怪しいところは、特に問題ありません。 >エラーが発生する場所ですが、どうやらリストの先頭にアクセスした瞬間にいきなり止まっているようです。リストの先頭のアドレスはちゃんと取得しています。 ほぼ、エラーの箇所が特定できているようですので、 「リストの先頭のアドレス」にアクセスする直前にそのリストの先頭アドレスの内容を、printfしてみては、いかがですか。ここが、破壊されている(他の値で書き換えられている)可能性があります。

ondy
質問者

お礼

再びありがとうございます。 tatsu99 さんの仰るような使い方はしていません。 アドバイスどおり、リストの先頭にアクセスする直前に中身を出力してみましたが値は正常でした。 Microsoft .NET Framework SDK では正常に動作するだけに原因がさっぱり特定できません。

ondy
質問者

補足

/*#8からの続き*/ if((fp=fopen("porousdata.txt","w"))==NULL){ printf("porousdata.txtを開く際にエラー発生\n"); exit(1); } fprintf(fp,"モデルのサイズ\n"); fprintf(fp,"Lx[mm] %lf\nLy[mm] %lf\nLz[mm] %lf\n", Lr*2, Lr*2, Lz); fprintf(fp,"球の種類数 1\n"); fprintf(fp,"球の平均半径[mm] %lf\n", (double)ave/counter); fprintf(fp,"球の最小半径[mm] %lf\n", rmin); fprintf(fp,"球の最大半径[mm] %lf\n", rmax); fprintf(fp,"目標空隙率[%] %lf\n========================================\n", porosity); fprintf(fp,"球の個数 %d\n", counter); fprintf(fp,"実際の空隙率[%] %lf\n", curp); fprintf(fp,"========================================\n球の番号 種類 x座標 y座標 z座標 半径 体積\n"); comp=&start; counter=0; while(cur!=comp){ fprintf(fp,"%d 0 %lf %lf %lf %lf %lf\n", counter++, comp->x + Lr, comp->y + Lr, comp->z, comp->r, 4.0*PI*pow(comp->r, 3)/3.0); comp=comp->next; }; fprintf(fp,"========================================\n"); fclose(fp); return 0; } char set(struct model *cur, double Lr, double Lz) { double m=RAND_MAX, theta, phi; struct model pre, *comp; long int i=0; char f; do{ f=0; do{ cur->x=rand()*2*Lr/m-Lr; cur->y=rand()*2*Lr/m-Lr; cur->z=Lz-cur->r; }while(pow(cur->x, 2)+pow(cur->y, 2) > pow(Lr - cur->r, 2)); comp=&start; while(comp!=cur){ if(pow(comp->x - cur->x, 2)+pow(comp->y - cur->y, 2)+pow(comp->z - cur->z, 2) < pow(comp->r + cur->r, 2)){ f=1; break; } comp=comp->next; }; if((i++)==50000) return 1; }while(f); for(i=0;i<STEP;i++){ pre=*cur; theta=rand()*PI/(2.0*m); phi=rand()*2.0*PI/m; cur->x=pre.x+DIFFERENCE*sin(theta)*cos(phi); cur->y=pre.y+DIFFERENCE*sin(theta)*sin(phi); cur->z=pre.z-DIFFERENCE*cos(theta); if(pow(cur->x, 2)+pow(cur->y, 2) > pow(Lr - cur->r, 2) || cur->z < cur->r) *cur=pre; else{ comp=&start; while(comp!=cur){ if(pow(cur->x - comp->x, 2)+pow(cur->y - comp->y, 2)+pow(cur->z - comp->z, 2) < pow(comp->r + cur->r, 2)){ *cur=pre; break; } comp=comp->next; } } } return 0; } double radius(char n, double *keisuu, double max, double min) { double x, y, m=RAND_MAX; char i; do{ x=rand()*(max-min)/m; y=rand()*100.0/m; x+=min; for(i=0;i<(int)n+1;i++) y-=keisuu[i]*pow(x, i); }while(y>0.0); return x/2.0; }

  • Oh-Orange
  • ベストアンサー率63% (854/1345)
回答No.4

★リスト構造の繋ぎを調べてみてはどう? ・リスト構造はポインタで繋ぎますが、もしかしたら正しくポインタで繋がれていなかったり、  または NULL ポインタのリストデータを参照していたりしませんか? ・実行時のエラーはリスト構造を追加、削除、検索などのどの動作で起きますか?  もしかしたら、リスト構造の1項目を削除したときにでるのでは? ・とにかくソースを全面的に見直して下さい。  アルゴリズムの異常までは『コンパイラ』でチェック出来ませんので、エラー・メッセージは  コンパイラ時には出ません。→実行時にエラーが出たりします。  私のミスで多かったものは、初期化していないポインタ(NULL)を参照してしまってエラーが  出ることが過去に数回あり、今はここの点に注意してデバッグなどを行っています。 ・多分、たまたま『Microsoft .NET Framework SDK』でコンパイルした方が、エラーがでないで  実行されているように思います。 ・また、おそらく『セグメントエラー』だと思いますって事は、エラーをきちんと把握していま  せんね。→もう一度どんなエラーがでたのか確認した方が良さそうです。 ・ソースファイルからリスト構造のデバッグを行って、どの関数を実行したらエラーが表示されるか  突き止めて下さい。 ・以上。私なりのアドバイスでした。

ondy
質問者

お礼

Oh-Orange さん。回答ありがとうございます。 エラーで止まるときは、あの「問題が発生したため、….exeを終了します。ご不便をかけて申し訳ありません。」というウィンドウが出て、コンソールにはなにも出力されず止まります。 リストは、削除することはしていなくて、追加、参照だけです。 エラーが発生する場所ですが、どうやらリストの先頭にアクセスした瞬間にいきなり止まっているようです。リストの先頭のアドレスはちゃんと取得しています。 アドバイスを参考にしてがんばってバグを見つけます。

  • tatsu99
  • ベストアンサー率52% (391/751)
回答No.3

私が経験した範囲で、良くあるケースとしては、以下のような場合です。 1.自動変数を初期化していないで、使用している場合。 int a;と変数を宣言すると、aの内容はなにが入っているかは、保証されません。うまく動く場合は、通常、0が入っている場合です。うまく動かない場合は、とんでもない値が入っている場合です。初期化しない値になにが入るかは、コンパイラ次第です。 2.範囲外を逸脱した、メモリの設定を行っている場合。 int a[100];と宣言された配列に、 a[i]=1000;を実行したとき、iの値は0~99の場合のみ、正しい動作をするはずです。iが100以上の場合の動作が、どのようになるかは、コンパイラ次第です。 int x[100]; int a[100]; int b[100]; と宣言し、a[100] = 123; を実行した時、a[100]が、どこの領域になるかですが、コンパイラのメモリの割付方法に依存します。 a[100]=123;が、b[0]=123;と同じ場合もあれば、x[0]=123;と同じ場合もあり得ます。また、全く別の領域の可能性もあります。従って、逸脱した範囲の、メモリの設定が、プログラムの実行に影響を与えない範囲で、すんでいる場合は、正常に動作しているように振る舞います。が、影響を与える場合は、実行にエラーとなります。

ondy
質問者

お礼

tatsu99 さん。回答ありがとうございます。 大変勉強になりました。変数が自動的に0に初期化されるのはグローバル変数だけだと思っていました。ローカル変数でも、コンパイラによって初期化されることがあるのですね。変数を割り当てる領域にも違いがあることも知りませんでした。 ソースの中で、バグの匂いがする箇所が3箇所ありまして、 一つは、main()関数の中でdouble型のポインタに対してmalloc()関数でメモリを割り当てて、そのポインタを他の関数に渡してその関数の中で確保した領域を配列としてアクセスしているところです。 二つ目は、 cur = &start; while(cur->next){ … cur = cur->next; }; というwhileループです。curは構造体へのポインタで、nextは構造体のメンバで同じ型の構造体型のポインタです。startは、リストの先頭の構造体です。cur->nextがヌルでない間繰り返すというコードのつもりです。 この二つ目が怪しいと思って、これを以下のように書き換えてみました。 /*curはリストの最後の構造体を指している*/ comp = &start; while(comp != cur){ … comp = comp->next; }; comp は、構造体へのポインタです。 これでコンパイルしてみても結果は同じでした。 私が気付いていない他の場所がエラーの原因かもしれませんが、ここに挙げたコードで何かまずいところがあればご教授願います。

  • MrBan
  • ベストアンサー率53% (331/615)
回答No.2

どんなソースなのか見ないと憶測になりますが、 一般には、実行時にまったく同じ動きをさせる方が難易度が高いです。 可搬性が高い、標準準拠の、正しいコードでないと全ての コンパイラで同じに動くようなコードにならなかったりするのが現実です。 実行時エラーが出るということは、大抵バグっている(正しいコードでない) ということなので、エラーになったときの挙動は違うのが普通です。 そして「たまたまエラーにならない(気づいてくれないだけで異常は異常)」という状況もよくあります。 この例では、VCの時はたまたま異常に気づかれなかっただけです。 正常ともいえませんし、何かが直っているわけでもないでしょう。 セグメントエラーがでるようなコードは十中八九バグっててデンジャラスなので、 ちゃんと修正するべきです。

ondy
質問者

お礼

MrBanさん。回答ありがとうございます。 コンパイラによって同じようには動かないのですね。 こういう状況がよくあることだと知って勉強になりました。 どんなコンパイラでも動作するようにバグ取りをがんばります。

  • koko_u
  • ベストアンサー率12% (14/116)
回答No.1

たとえばリストの端を越えた場所にアクセスしているとか。 まぁ、十中八九ポインタの使い方が拙い箇所があるのでしょう。 ソースコードを見てわからないなら、テストコードを書いて問題が発生する操作を特定していくしかないと思う。

ondy
質問者

お礼

koko_u さん。回答ありがとうございます。 リストの端を越えてアクセスしていないかいろいろいろいろ確かめましたが、まだバグを見つけられません。 テストコードというのは、怪しいコードの周囲でコンソールに何か出力してみることですよね。 まだ、何か見落としているところがあると思うのでバグ取りがんばります。