- ベストアンサー
c言語のmalloc関数と2次元配列について
・mallocとreallocのAPPを作成しています、下記は単純化しました。 「質問-1」 ・while(1){...以下を無効にした場合、正常に終了します。 ・有効にして、最初に999を入力した場合、エラー表示されます。 ・この理由が分かりません。 「質問-2」 ・有効にして、初期数値(例えば11)を入力の場合、正常表示されます ・続けて数値(例えば15)を入力した場合、エラー表示されます。 ・この理由が分かりません。 *************************************************************** #include <stdio.h> #include <stdlib.h> void MylnOut(void); int **map; int X=10,Y=10,i,j; //************************************************************** // MAIN //************************************************************** int main() { char str[64]={""}; char *s="変更数値を入力(999で終了).... "; /* 2次元配列確保と初期表示 */ map=(int **)malloc(sizeof(int *)*X); for(i=0;i<X;i++) map[i]=(int *)malloc(sizeof(int)*Y); MylnOut(); /* 変更数値入力 */ // while(1){ // printf(s); // gets(str); // X=atoi(str); // if(X==999) break; /* 領域変更と表示 */ // map=(int **)realloc(map,sizeof(int *)*X); // for(i=0;i<X;i++) // map[i]=(int *)realloc(map[i],sizeof(Y)); // MylnOut(); // } /* 領域開放 */ for(i=0;i<X;i++) free(map[i]); free(map); return 0; } //************************************************************** // 入力・表示 //************************************************************** void MylnOut(void) { for(j=0;j<Y;j++) for(i=0;i<X;i++) map[i][j]=55; for(j=0;j<Y;j++){ for(i=0;i<X;i++) printf("%3d",map[i][j]); printf("\n"); } }
- みんなの回答 (8)
- 専門家の回答
質問者が選んだベストアンサー
>・領域は変更されますが....偶然メモリに残っていた >この辺をもう少し説明下さい。 realloc()では、拡張したメモリ領域のクリア(0x00で埋める)までは実行しません。 とすると、そこには以前に使用していた時に書き込まれた情報が残っていることになります。 ついでに言うと…realloc()が失敗した場合の対応が考慮されていません。 領域サイズを縮小する場合にrealloc()が失敗することはまずないと思われます(0ではない)が、拡大する場合は失敗する可能性があります(縮小時よりは可能性が高い)。 メモリの確保、解放を繰り返して、ヒープメモリ上にフラグメントが発生している状態では失敗することもあります。 realloc()に失敗してもrealloc()の第1引数に渡した領域は開放されていませんので、アドレスが行方不明になりメモリリークすることになります。 http://www.google.co.jp/search?hl=ja&source=hp&q=realloc+%E5%A4%B1%E6%95%97&btnG=Google+%E6%A4%9C%E7%B4%A2&lr=&aq=4&oq=realloc
その他の回答 (7)
- m-take0220
- ベストアンサー率60% (477/782)
動作させてチェックしたわけではないですが、ぱっと見た限りでは問題ないように思います。 問題が出たとしても、原因に心当たりがあれば、修正できるんじゃないかと思います。
お礼
・多くの親切な回答有難うございました。 ・おかげさまで、少しは分かってきました。 ・Yがもう少し大きな値に対応する様に変更し、対応出来ました。 有難うございました。
- m-take0220
- ベストアンサー率60% (477/782)
map だけでなく、map[i] に確保した領域についても数えてください。 (例) (realloc は成功すれば確保された領域の数は変わらないので、省略しています。) ・最初に11を入力した場合 map に malloc で領域確保1回 map[0]~map[9] に malloc で領域確保10回 map[0]~map[10] を free で解放11回 map を free で解放1回 確保11回に対し解放が12回あります。
補足
ご教示ありがとうございます。 ・mallocで擬似的な2次元配列は前も使いましたが 今回の方法は初めてです、勉強になりました。 ・下記の様に修正し、TESTしました。 ・一応これで「エラー」は表示されません。 ・不備に点、ご教示頂ければ幸いです。 #include <stdio.h> #include <stdlib.h> #include <string.h> void MylnOut(int ,int ,int **); //************************************************************** // MAIN //************************************************************** int main(void) { int **map; int X,Y,i,j,n,XX; /* 2次元配列確保と表示 */ printf("初期値.... \n"); X=XX=10; Y=10; map=(int **)malloc(sizeof(int)*X); for(i=0;i<X;i++){ map[i]=(int *)malloc(sizeof(int)*Y); } MylnOut( X, Y, map); /* 領域変更と表示 */ for(n=1;n<11;n++){ X=rand()%22+5; Y=rand()%6+5; printf("\n変更..%2d回目 X=%2d Y=%2d\n",n,X,Y); if(X>XX){ map=(int **)realloc(map,sizeof(int)*X); for(i=0;i<XX;i++) map[i]=(int *)realloc(map[i],sizeof(int)*Y); for(i=XX;i<X;i++) map[i]=(int *)malloc(sizeof(int)*Y); } if(X<XX){ for(i=X;i<XX;i++) free(map[i]); map=(int **)realloc(map,sizeof(int)*X); for(i=0;i<X;i++) map[i]=(int *)realloc(map[i],sizeof(int)*Y); } MylnOut( X, Y, map); XX=X; } /* 領域開放 */ for(i=0;i<X;i++) free(map[i]); free(map); return 0; //************************************************************** // 入力・表示 //************************************************************** void MylnOut(int X,int Y,int **map) { int i,j; for(j=0;j<Y;j++) for(i=0;i<X;i++) map[i][j]=X; for(j=0;j<Y;j++){ for(i=0;i<X;i++) printf("%3d",map[i][j]); printf("\n"); } }
- m-take0220
- ベストアンサー率60% (477/782)
> /* 領域変更と表示 */ > map=(int **)realloc(NULL,sizeof(int *)*X); これでは、以前に map に代入されていた値はすべてなくなってしまいます。 ここを元に戻したとして、前回指摘したように map の数が増えた場合に増えた部分の map[i] にmallocなどで確保されたポインタが代入されていませんし、 map の数が減った場合に減った部分の map[i] は領域が開放されていません。 そもそも、 map[i] に必要な領域が sizeof(int)*Y で Y=10 が変わらないのであれば、realloc する必要などありませんよね。 確保した領域は開放しなければなりません。realloc は NULL を渡した場合のみ新たに領域を確保します。今のコードで realloc に NULL を渡しているのは、冒頭でした記したコードのみですから、realloc では新たな領域は確保されないものとして、以下の状況で領域を確保した回数と開放した回数を数えてみてください。 ・最初に999を入力した場合 ・最初に11を入力した場合 ・最初に11、次に15を入力した場合 ・最初に9を入力した場合
補足
ご教示ありがとうございます。 ・下記の様に書換えてTESTしました。 >・最初に999を入力した場合 ・初期確保で1回、最後の999の領域開放で1回・・・実行時正常に終了しました >・最初に11を入力した場合 ・初期確保で1回、reallocのなかで開放 ・11入力で変更確保、最後の999の領域開放で1回・・・実行時正常に終了しました >・最初に11、次に15を入力した場合 ・初期確保で1回、reallocのなかで開放 ・11入力で変更確保、reallocのなかで開放 ・15入力で変更確保、最後の999の領域開放で1回・・・実行時正常に終了しました >・最初に9を入力した場合 ・初期確保で1回、reallocのなかで開放 ・9入力で変更確保、reallocので開放 ・15入力で変更確保、最後の999の領域開放で1回・・・実行時正常に終了しました ■最初に9、次に10を入力、次に999を入力、した場合 これも正常に終了しました。 ■ >そもそも、 map[i] に必要な領域が sizeof(int)*Y で Y=10 が変わらないのであれば、realloc する必要などありませんよね 上記で「Y」の値は、本来Xと同様に変化させる事を目標としていますが、いま「X」のみの変化に挑戦しています。 何となく、動きますが、時として「エラー表示」が出ます、不安定だと感じています。 不備の点ご指摘頂ければ幸いです。 よろしくお願いします。 #include <stdio.h> #include <stdlib.h> #include <string.h> void MylnOut(int ,int ,int **); //************************************************************** // MAIN //************************************************************** int main(void) { int **map; int X,Y,i,n; char *s="変更数値を入力(999で終了).... "; /* 2次元配列確保と表示 */ printf("初期値.... \n"); X=10; Y=10; map=(int **)malloc(sizeof(int *)*X); for(i=0;i<X;i++) map[i]=(int *)malloc(sizeof(int)*Y); MylnOut( X, Y, map); /* 領域変更と表示 */ while(1){ printf(s); scanf("%d",&n); if(n==999) break; X=Y=n; Y=X; map=(int **)realloc(map,sizeof(int *)*X); for(i=0;i<X;i++) map[i]=(int *)realloc(map[i],sizeof(int)*Y); MylnOut( X, Y, map); } /* 領域開放 */ for(i=0;i<X;i++) free(map[i]); free(map); return 0; //************************************************************** // 入力・表示 //************************************************************** void MylnOut(int X,int Y,int **map) { int i,j; for(j=0;j<Y;j++) for(i=0;i<X;i++) map[i][j]=X; for(j=0;j<Y;j++){ for(i=0;i<X;i++) printf("%3d",map[i][j]); printf("\n"); } }
- m-take0220
- ベストアンサー率60% (477/782)
たとえば、 int X; とすると、コンパイラは X の為のメモリを確保します。そのため、 X=10; のように値を代入すると、そのメモリの値が変わります。それでは、 X=10; を実行する前の X の値は何かというと、誰にもわかりません。コンパイラはメモリを確保しても初期化はしませんから。以前にそのメモリを使っていたプログラムが書きこんでいた値がそのまま残っているだけだし、そもそもこのプログラムを複数回実行したとして、 X 用に確保されるメモリの位置が毎回同じとも限らないので、毎回値が変わる可能性もあります。 map[0]~map[9] については、malloc が返す値を代入しているので、意味のある値が入っているはずですが、 map[10] は特に初期化していないので、上記の X と同じ状況です。 realloc でサイズを変更する領域は、 malloc、calloc、realloc で確保した領域でなければならないので、少なくともこのプログラムで初期化したことのないポインタを渡していいはずがありません。 また、偶然動作してはいますが、 >/* 領域変更と表示 */ > map=(int **)realloc(map,sizeof(int *)*X); > for(i=0;i<X;i++) > map[i]=(int *)realloc(map[i],sizeof(Y)); ここの最後は sizeof(Y) ではなく sizeof(int)*Y ですよね。 sizeof(Y) だと int 1つ分のメモリしか確保されませんから、 MylnOut 内で確保した領域を超えてアクセスしていることになります。 また、今までは map の領域が拡大されるような入力例しか出てきていませんが、例えば最初に 9 を入力したとすると、map の領域は 0~8 の9個に減少します。その際、初期化時の10回目に malloc で確保され、 map[9] に代入されていたポインタの値が消失します。当然、最後の解放処理も map[0]~map[8] に対してしか行われませんから、確保した領域の解放漏れ(メモリリーク)が発生します。
補足
・経験不足者ですが、ご教示を参考に下記の様に書換えました。 ・領域確保エラーには対応していませんが。これで一応、変更数値の入力エラーは表示はされません。 ・不備の点、ご指摘いただければ幸いです。 ・これを、WindowsAPPに取り入れようと考えています。 #include <stdio.h> #include <stdlib.h> #include <string.h> void MylnOut(int ,int ,int **); //************************************************************** // MAIN //************************************************************** int main() { int **map; int X=10,Y=10,i; char str[64]={""}; char *s="変更数値を入力(999で終了).... "; /* 2次元配列確保と初期表示 */ map=(int **)malloc(sizeof(int *)*X); for(i=0;i<X;i++) map[i]=(int *)malloc(sizeof(int)*Y); MylnOut( X, Y, map); /* 変更数値入力 */ while(1){ printf(s); gets(str); if(!strcmp(str,"999")) break; X=atoi(str); /* 領域変更と表示 */ map=(int **)realloc(NULL,sizeof(int *)*X); for(i=0;i<X;i++) map[i]=(int *)realloc(map[i],sizeof(int)*Y); MylnOut( X, Y, map); } /* 領域開放 */ for(i=0;i<X;i++) free(map[i]); free(map); return 0; } //************************************************************** // 入力・表示 //************************************************************** void MylnOut(int X,int Y,int **map) { int i,j; for(j=0;j<Y;j++) for(i=0;i<X;i++) map[i][j]=55; for(j=0;j<Y;j++){ for(i=0;i<X;i++) printf("%3d",map[i][j]); printf("\n"); } }
- m-take0220
- ベストアンサー率60% (477/782)
>「質問-1」 >・while(1){...以下を無効にした場合、正常に終了します。 >・有効にして、最初に999を入力した場合、エラー表示されます。 >・この理由が分かりません。 mapにはmallocでsizeof(int*)*Xのメモリを確保していますが、その時点でのXは10です。while(1)以降でXの値を変更(この場合は999)しているのに、 > for(i=0;i<X;i++) free(map[i]); という形で領域解放しようとしているので、map[10]~map[998]という確保されていない領域にアクセスしています。 >「質問-2」 >・有効にして、初期数値(例えば11)を入力の場合、正常表示されます >・続けて数値(例えば15)を入力した場合、エラー表示されます。 >・この理由が分かりません。 おそらく、11を入力した時点で問題があります。 > map=(int **)realloc(map,sizeof(int *)*X); これによりmapに確保された領域は変更されますが、map[10]はreallocで領域サイズが拡大された際に偶然メモリに残っていた値になります。当然map[0]~map[9]のようにmallocで割り当てた領域のポインタではありません。そのような値に対して > map[i]=(int *)realloc(map[i],sizeof(Y)); のような処理を行うことは、非常に問題があると思います。
補足
回答有難うございます。 「質問-2」の件についてもう少し質問します。 これによりmapに確保された領域は変更されますが、map[10]はreallocで領域サイズが拡大された際に偶然メモリに残っていた値になります。 上記の中で ・領域は変更されますが....偶然メモリに残っていた この辺をもう少し説明下さい。 ちなみに ・while(1){...以下を有効にし、最初に15を入力した場合、正常表示されます。 ・続けて16を入力するとエラー表示されます。
- kent_a
- ベストアンサー率18% (36/199)
どんなエラーがでるのかわからないのでなんともいえませんが... 質問1 初期化時にX=10、Y=10で領域を獲得していますよね いきなり999にするX=999になりますね X=10で獲得した領域に対して、X=999で解放しようとしているのでエラーなのでは? 質問2 11の時に大丈夫ってのはわかりませんが reallocは獲得済みのメモリのサイズ変更だった気がするのでmap[10]以降は領域(メモリ)が獲得されていないのでエラーになるのでは?
補足
■「質問1」への回答について ・ご指摘の通りでした。 ・#include <string.h> if(!strcmp(str,"999")) break; X=atoi(str); ・上記の様に変更しました。 ・これで ・while(1){...以下を無効にした場合、正常に終了します。 ・有効にして、最初に999を入力した場合でも、正常に終了します。 ■「質問2」への回答について ・上記(質問ー1の回答)の様に変更後 ・ちなみに、while(1){...以下を有効にした場合で、最初に15を入力でも正常表示されます。 ・続いて16入力でエラーとなります ・reallocは獲得済みのメモリのサイズ変更・・・・これは分かるのですが・・・
- asuncion
- ベストアンサー率33% (2127/6289)
「エラー表示」の具体的な内容を書いてください。
補足
説明不足ですいませんでした。 ■「エラー表示」について *********************************************************** 問題が発生したため、***.exeを終了します。ご不便をおかけて申し訳ありません。 作業途中・・・・・ この問題を「マイクロソフト」に報告ください。 ************************************************************ ・一部ですが以上の様なエラー表示です。 ・コンパイルエラーは無く実行時エラーです。 .Borland C++ Compiler 5.5.1,TuboDebugger 5.5を使用しています。
お礼
・親切な回答有難うございました。 ・下記のHP紹介有難うございました。 http://www.google.co.jp/search?hl=ja&source=hp&q=realloc+%E... ・実装例が出ていて多いに参考になりました。 どうも有り難うございました。
補足
ご教示有難うございます。 参考のHP大変有難うございます。 その中での内容に質問させてください。 「質問ー1」 >一度開放した領域にはアクセスしてはいけません・・・・ ・これは、書込みも、参照も両方ダメという事でしょうか。 ・何だかの方法で参照だけはOKでしょうか 「質問ー2」 >memset()、memcpy()関数の使用について ・他の記事で動的にメモリー確保をした場合、メモリー配列がバラバラになると有りましたが。 その辺の解説を頂ければ幸いです。