ページ 1 / 1
擬似的配列の動的確保とその解放
Posted: 2008年7月02日(水) 01:53
by スプー
目的の配列 er[301]を「double *er=NULL;」と宣言して
その後でcallocにより領域の動的確保、初期化をしました。
そしてファイルを読み込んで値を代入して最後にヒープ領域を解放するプログラムを作成しました。
初めはborland5.5で行っていたのですが配列を2次元に拡張するとうまくいかなくなりました。
自分なりに条件を変えながら変数を調べたりした結果
最初に確保したアドレスと代入した後のアドレスが変わっている事までは分かったのですが
それがなぜそうなるのかまでは分かりませんでした。
コンパイラを変えてVC++2008をインストールして試してみたところ1次元配列のプログラムで
一応目的の結果は達成しているものの最後のヒープ解放でエラーが出ました。以下エラー文です。
# WindowsによってDataCombine.exe(このプログラム名)でブレークポイントが発生しました。
# ヒープが壊れていることが原因として考えられます。
# DataCombine.exeまたは読み込まれたDLLにバグがあります。
ソースも載せておきます。
#include <stdafx.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
FILE *fp;
char buf[128];
double *freq=NULL, *er=NULL, *ei=NULL;
int i;
freq=(double *)calloc(301, sizeof(double));
if(freq==NULL) goto ErrorEnd;
er=(double *)calloc(301, sizeof(double));
if(er==NULL) goto ErrorEnd;
ei=(double *)calloc(301, sizeof(double));
if(ei==NULL) goto ErrorEnd;
if((fp=fopen("c:\\w001.prn", "r"))==NULL){
fprintf(stderr, "%sは存在しない\n", "w001.prn");
goto ErrorEnd;
}
fgets(buf, 128, fp); //ファイルの1行目はとばす
i=0;
while(!feof(fp)){
if(fgets(buf, 128, fp)!=NULL)
sscanf(buf, "%lf %lf %lf", &freq, &er, &ei);
freq/=1000000000;
i++;
}
fclose(fp);
for(i=0; i<301; i++) printf("%lf %lf %lf\n", freq, er, ei);
ErrorEnd:
free(freq);
free(er);
free(ei);
getchar();
return 0;
}
読み込むファイルw001.prnは1行目は「freq er ei」というタイトル、
2行目からはタブで区切られた3つの数値が301行あるというものです。
私の環境としてOSはvista home premiumです。
どこに問題があるでしょうか。分かる方どうぞご教授お願いします。
よろしくお願いします。
Re:擬似的配列の動的確保とその解放
Posted: 2008年7月02日(水) 06:26
by 組木紙織
正しく入力するファイルがあれば問題なく動くプログラムだと思います。
問題があるとすれば入力するファイルかな。
多分行数が301行を超えているとか、数値以外が入っているとか
そんな感じの問題だと思います。
#最後のgetchar()が気に入らないけど
Re:擬似的配列の動的確保とその解放
Posted: 2008年7月02日(水) 07:23
by box
入力データを5個とか10個とか少ない数にして、
コード中の301を個数に対応するように修正してから
コンパイル~実行したらどうなるでしょうか。
また、差し支えなければ、入力データの先頭数行分を見せていただけますか?
こちらでも同じ現象が起きるかどうか確認してみたいです。
Re:擬似的配列の動的確保とその解放
Posted: 2008年7月02日(水) 17:11
by スプー
返答ありがとうございます。
お二方の意見を元に配列の数と入力ファイルを
いろいろ変えて調べたところ原因が分かりました。
入力ファイルの一番最後は
1行目はとばしているため
302行目:25.0000 29.0168 34.8939[改行]
303行目:[EOF]
となっていました。
そのため、302行目をfgetsで読み込んだあとiはインクリメントされて301となり
ファイルポインタはまだEOFに到達していないのでwhileループは続行して
303行目でfgetsを行いEOF=-1なのでエラーとはならず、
定義していないfreq[301]にアクセスして書き込むため
ヒープが壊れてfreeで解放するときにエラーが出たのだと思います。
よって次のいずれかの方法で正常に動くようになりました。
1.ファイルの最後の改行を削除する。
2.callocによる領域確保を302個分とる。
fgetsによるファイルポインタの動きとEOFとNULLを混同していたために起きたミスでした。
ご助言していただきありがとうございました。
>組木紙織さん
>#最後のgetchar()が気に入らないけど
VC++は初めて使ったので勝手が良く分からなかったので
コンパイルしたあと直接実行ファイルから起動してました。
そのとき一瞬で閉じてしまうのでgetchar()を入れたのですが…。
汚くてすいません。
他に処理を一時停止するような便利なコマンドや関数があるのでしょうか。
Re:擬似的配列の動的確保とその解放
Posted: 2008年7月03日(木) 23:05
by 組木紙織
>他に処理を一時停止するような便利なコマンドや関数があるのでしょうか。
bcc5.5を使っていたのならコマンドプロンプトから実行することができるはずなので、
コマンドプロンプトから実行してみてください。
VC++ならデバックなしで実行をしてみるのも一つの方法です。
そうすれば処理を一時停止にしなくても結果が見れるはずです。
以下エラー検査すべてした(はずの)ソースコード。
2次元動的配列も使っています。
/*
インクルードできなかったのでコメントアウト
#include <stdafx.h> */
#include <stdio.h>
#include <stdlib.h>
#define BUF_SIZE 128 /*バッファサイズ*/
#define LINE_SIZE 3 /*数字列の行数*/
#define ROW_SIZE 3 /*列数*/
void myFree(double** pd)
{
int i;
for(i=0;i<ROW_SIZE;i++){
free(pd);
}
free(pd);
}
int main(void)
{
FILE *fp;
char buf[BUF_SIZE];
double **freqs;
int i,j;
char* file_flag;
freqs = (double **) malloc(ROW_SIZE*sizeof(double*));
if(freqs == NULL ){
printf("erroe\n");
return 0;
}
for(i=0;i<ROW_SIZE;i++){
freqs = malloc(LINE_SIZE*sizeof(double));
/*領域確保に失敗したら今までに確保した領域を解放して終了*/
if(freqs == NULL){
printf("error:%d\n",i);
for(j=0;j<=i;j++){
free (freqs[j]);
free(freqs);
}
return EXIT_FAILURE;
}
}
/* if((fp=fopen("c:\\w001.prn", "r"))==NULL){ */
if((fp=fopen("w001.txt", "r"))==NULL){
fprintf(stderr, "%sは存在しない\n", "w001.txt");
myFree(freqs);
return EXIT_FAILURE;
}
fgets(buf, 128, fp); /*ファイルの1行目はとばす*/
i=0;
for(i=0;i<LINE_SIZE;i++){
while(!feof(fp)){
file_flag = fgets(buf,128,fp);
/*fgets()の読み込みが失敗したとき*/
if(file_flag == NULL){
printf("file error1\n");
myFree(freqs);
return EXIT_FAILURE;
}else{
/*数字が正しく入ってなかったとき*/
if(sscanf(buf, "%lf %lf %lf", &freqs[0], &freqs[1], &freqs[2])!=3){
printf("file error22\n");
myFree(freqs);
return EXIT_FAILURE;
}
}
goto FILENEXT;
}
/*fgets()の読み込みまでが正確だがファイルが短かった時*/
printf("file error2\n");
myFree(freqs);
return EXIT_FAILURE;
FILENEXT:
continue;
}
fclose(fp);
for(i=0; i<LINE_SIZE; i++) printf("%lf %lf %lf\n", freqs[0], freqs[1], freqs[2]);
myFree(freqs);
return EXIT_SUCCESS;
}
久々にCを触ったのでちょっと自信がありません。
突っ込みどころがあったらご自由にどうぞ。
Re:擬似的配列の動的確保とその解放
Posted: 2008年7月04日(金) 01:35
by スプー
組木紙織さん。
返信ありがとうございます。
なるべくコマンドプロンプトから呼んで余計なコードを増やさないように努力したいと思います。
また、改良したソースありがとうございます。
自分でも入力ファイルの改行を消したり、不要な配列要素を足したりするのは
なんだか汚いとは思ってはいたのですが…。
whileループをfor文で囲むところやそれぞれのエラー処理など参考にいたします。
一つ気になったのはmallocのところです。
私はmallocは領域を確保するだけで、callocだとプラス初期化もしてくれるものだと考えて、
何が入っているか分からないよりは0が入っていた方がいいだろうと、callocを使いました。
また、mallocの使い方を調べていると領域を確保したあと
memsetで明示的に0を代入しているソースコードを見たこともあります。
ところが、自分でmallocで領域を確保した直後の配列の中身を見てみるときちんと0が入っていました。
これは処理系によっては0になるとは限らないと考えていいのでしょうか。
そもそも、自分の環境で正常に動いていて自分だけが使うプログラムなら
処理系のことも関係なくどっちでも構わないという話なのでしょうか。
初期化に関連して配列のポインタ変数を宣言するときも「NULLを入れておいたほうがいい」と
どこかで聞いたことがあったので私はそうしていたのですが、これも好みによるのでしょうか。
質問ばかりですいません。
Re:擬似的配列の動的確保とその解放
Posted: 2008年7月04日(金) 08:32
by 組木紙織
malloc()に関しては領域の確保のみなので、0が入っていたのはたまたまです。
文字列を扱うときに配列を0で初期化をしておくと文字列終端が'\0'と定義してるので
アルゴリズム的には問題があるのに、たまたまうまく動く場合があったりするので、
プログラムに問題があっても動くよりは、動かないほうがよいと考えて自分でプログラムを書くときには
malloc()を使うようにしているだけです。
(malloc()+menset()=calloc()を考えていいです)
配列のポインタに関してはNULLで初期化や、NULLを解放後に代入をしておくと、二重開放や確保していない領域の開放の
問題に対処することができるので、そのようにしたほうがいいという人もいますが、
これもきちんとプログラムを組めば対処できるので、自分でプログラムを書くときは上記のようにしています。
このあたりは好みの問題がおおいですけどね。
Re:擬似的配列の動的確保とその解放
Posted: 2008年7月04日(金) 10:25
by バグ
VisualStudio2008をお使いでしたら、ブレークポイントが使用できると思います。
bccコンパイラをお使いでしたら、『Turbo Debugger』というデバッガがあったと思います。使い方が分かりづらいかもしれませんか非常に便利です。また、フリーの開発環境である『BCC Developer』という便利なソフトがあります(下記URL参照)。この環境にコンパイラとデバッガを両方共登録しておけば、更に開発が楽になりますよ(^-^)
Re:擬似的配列の動的確保とその解放
Posted: 2008年7月04日(金) 10:26
by バグ
Re:擬似的配列の動的確保とその解放
Posted: 2008年7月04日(金) 20:40
by スプー
いろいろ疑問が解決しました。
ありがとうございます。
バグさんの教えてくれた『Turbo Debugger』と『BCC Developer』は
名前は聞いたことがあるのですが使い方が良く分からなかったので敬遠してました。
これを機会に覚えて使ってみようかと思います。
回答してくださったみなさん、ほんとうにありがとうございました。
また分からなくなったら質問するかもしれませんがその時はよろしくお願いします。