- ベストアンサー
reallocでうまくメモリを拡張出来ていない?気がしますが・・・
OS:WindowsXP SP3 コンパイラ:Visual C++ 2008Express Edition with Service Pack 1 質問なんですが、 ttp://homepage3.nifty.com/mmgames/c_guide/ ↑こちらの練習問題19の3-1なんですが、4人以上のデータを入力して showdata関数で表示させると4人目からのデータが入力したデータと 違います。 (reallocのところが間違っているような気がするのですが?です) どなたかアドバイスお願いします。 他の問題点も良ければアドバイス頂けると幸いです。 *******************以下自分で書いたソース******************** #include <stdio.h> #include <stdlib.h> //構造体 typedef struct { char name[64]; int age; int sex; } stat; //プロトタイプ宣言 int status(stat * ,int *); void showdata(stat *, int); int main() { int cnt, max = 3; //構造体ポインタの宣言 stat *data; //動的配列の宣言 data = (stat *)malloc(sizeof(stat) * max); if ( data == NULL ) { exit(0); } //入力用関数の呼び出し cnt = status(data, &max); //出力用関数の呼び出し showdata(data, cnt); //メモリの開放 free(data); return 0; } int status(stat *data, int *max) { int i = 0; //年齢に-1が入力されるまでループ do { if ( (i + 1) > (*max) ) { *max += 10; data = (stat *)realloc(data, sizeof(stat) * (*max) ); if ( data == NULL ) { exit(0); } } printf("名前入力:"); scanf("%63s", data[i].name); getchar(); fflush(stdin); printf("年齢入力(-1入力で入力終了):"); scanf("%d", &data[i].age); getchar(); //1か2でない場合再入力 do { printf("性別を入力(1.男性 2.女性) <1 or 2>:"); scanf("%d", &data[i].sex); getchar(); if ( (data[i].sex < 1) || (data[i].sex > 2) ) { data[i].sex = 3; printf("1か2で入力してください\n"); } } while ( (data[i].sex) == 3 ); i++; } while ( (data[i-1].age) != -1 ); return i - 1; } void showdata(stat *data, int cnt) { int i; for ( i = 0; i < cnt; i++ ) { printf("\n%d人目のデータ\n", i + 1); printf("名前:%s\n", data[i].name); printf("年齢:%d\n", data[i].age); if ( data[i].sex == 1 ) { printf("性別:男性\n"); } else { printf("性別:女性\n"); } } return; }
- みんなの回答 (9)
- 専門家の回答
質問者が選んだベストアンサー
とりあえず理解を助けるためにアドバイス。 ポインタとは、 「"値"が格納された場所を示すための"値"」 です。 これは、ポインタが場所を示すものであるということのほかに、ポインタ自身が値に過ぎないことを示します。すなわち、ポインタがポインタを指すことができます。 int func(int a*) { *a = 10; } int main(void) { int a = 0; func(&a); printf("%d", a); //10 } このコードは読めますよね? 重要なのは、書き換えたい変数の型と、関数に渡す型の関係です。 int型の変数を書き換えたいのでint*型の値を渡しています。 一般化すれば、 「T型の変数を呼び出し先の関数で書き換えたいとき、渡すべき値はT*型となる。」 ということになります。 そして今回はTがstat*なので、渡すべき型はstat**なのです。 どういうポインタを使えばいいのか悩む必要はありません。型をみればすぐにわかります。
その他の回答 (8)
- S117
- ベストアンサー率40% (18/45)
#8 >大体分かるんですがint func(int a*)が分かりません。 すみません、そこはただの書き間違えです。 int *a で、読んでください。 混乱させて申し訳ありません。
お礼
>すみません、そこはただの書き間違えです。 >int *a >で、読んでください。 >混乱させて申し訳ありません。 そうだったんですね、こちらこそ失礼しました。 最後まで付き合って頂いてありがとうございました!
- asuncion
- ベストアンサー率33% (2127/6289)
>自分ではcnt = status(data, &max);のdataは配列dataの先頭アドレス"のつもり"で引数にしていました。 そのdataの値を、status関数の中でrealloc関数を使って書き換えていますね。 ということは、呼び出し元からstatus関数を呼び出す際、 dataのアドレス、つまり&dataを引数として渡さねばなりません。 今、dataはstat *型ですので、引数として渡すのはstat **型となります。
補足
ありがとうございます。 仮引数がポインタのポインタになっているから、実引数は&data(箱のアドレスを持っているポインタ自身のアドレス:うまく表現出来てないかもしれま せんが頭の中では理解出来ました)になるということですね。 あと、入力のところで悩んでたんですが色々調べて scanf("%d", &(*data)[i].age) という風にしたらうまく実行出来るようになりました。
- chie65536(@chie65535)
- ベストアンサー率44% (8768/19897)
>status()内でrealloc()が成功したとしてもアドレスが変わっている為に >問題が出ているということでしょうか? >main()内でrealloc()を使うようにするしかないですか? 貴方、自分で >//入力用関数の呼び出し >cnt = status(data, &max); って呼んでおいて >int status(stat *data, int *max) >{ >(略) >if ( (i + 1) > (*max) ) { >*max += 10; ってやってるよね? これって「mainで定義されてるmaxを、statusの中で書き替えてる」んじゃないの? だったら >status()内でrealloc()が成功したとしてもアドレスが変わっている為に >問題が出ているということでしょうか? >main()内でrealloc()を使うようにするしかないですか? って悩むのはおかしいよね? 「maxをstatusの中で書き替えてるのと同じ方法で、dataもstatusの中で書き替えれる」よね? それが理解できてないのに「誰かが書いたコードを鵜呑みにして、理解せずに使う」から、こういう事になる。 「本当に理解して書いてるなら、ここで質問する筈がない」と思うんだけど、どうだろう? >stat **にしたのですがコンパイルでエラーになり、調べてみたら >stat **がポインタのポインタらしい事はわかったのですが・・・ >色々試してみてもうまくいきませんでした statusを呼ぶ時に、maxの中身をstatusの中で書き替えてもらうから >cnt = status(data, &max); って感じで「maxに&を付けてる」でしょ? だったら、何でdataの方にも&を付けないの? 「何の為にポインタ渡しをしているか?」が理解できない限り、質問文にあるプログラムは「貴方にとって、まだ高度過ぎて、今、手を付けるべきじゃない」ので、そこを勉強し直してから出直した方が良いですよ。
補足
すみません、自分ではcnt = status(data, &max);のdataは配列dataの 先頭アドレス"のつもり"で引数にしていました。 なのでdataには&を付けませんでした。 **(ポインタのポインタ)はここで初めて目にしたので、 未だになぜ**じゃないとダメなのかもわかりません。 まだ勉強始めたばかりで知らないことのほうが多い為、 >status()内でrealloc()が成功したとしてもアドレスが変わっている為に >問題が出ているということでしょうか? >main()内でrealloc()を使うようにするしかないですか? こういう疑問も沸いてきました。 上級者の方から見るとトンでもない事言ってるんでしょうね・・・。 すみませんでした。
- Tacosan
- ベストアンサー率23% (3656/15482)
status を呼び出す方も修正しておいてね. あとついで: Visual Studio では fflush(stdin); とできますが, ほかの処理系でも使えるとは思わないでください.
補足
ありがとうございます。 呼び出しのほうは、 cnt = status(&data, &max); としてみました。 2回目の名前入力でエラーが出るようになりましたが 今、どこがダメなのか探してる途中です。 fflush(stdin)はそのままにしてありますが参考になりました。
- php504
- ベストアンサー率42% (926/2160)
int status(stat **data, int *max) if ( ( data2 = (stat *)realloc(*data, sizeof(stat) * (*max) ) ) != 0 ) { *data = data2; } else { free(*data); *data = NULL; exit(0); }
補足
ありがとうございます。 回答通りにしてint status()関数内のdata.xxxの.を->に変えて やってみましたが、今度は2回目の名前入力が終わったところで エラーが出るようになってしまいました。 もう少し頑張ってみます。
- Tacosan
- ベストアンサー率23% (3656/15482)
関数 status の第1引数を stat ** にすれば問題は 1つ解決. あと, realloc を使うときには if ((tmp = realloc(p, ....)) != 0) { p = tmp; } else { /* 確保失敗 */ } とやるのが鉄則. こうすれば realloc に失敗しても最低限なんとかなります.
補足
>関数 status の第1引数を stat ** にすれば問題は 1つ解決 stat **にしたのですがコンパイルでエラーになり、調べてみたら stat **がポインタのポインタらしい事はわかったのですが・・・ 色々試してみてもうまくいきませんでした >if ((tmp = realloc(p, ....)) != 0) { >p = tmp; >} else { >/* 確保失敗 */ >} こちらはなんとかなりました ありがとうございます if ( ( data2 = (stat *)realloc(data, sizeof(stat) * (*max) ) ) != 0 ) { data = data2; } else { free(data); exit(0); } としました よろしければstat **についてもう少しヒントをいただけませんか?
- Wr5
- ベストアンサー率53% (2173/4061)
もう1つ忘れていました。 status()内でrealloc()が成功した場合、呼び出し元のmain()のローカル変数dataが指している先のメモリブロックは解放済みということになりますので、 その後のアクセス(showdata()内での参照)で吹っ飛ぶ場合があります。 # 環境依存…てすが。
補足
レスありがとうございます。 status()内でrealloc()が成功したとしてもアドレスが変わっている為に 問題が出ているということでしょうか? main()内でrealloc()を使うようにするしかないですか? さらに混乱してきました・・・。 ># 失敗した(拡張できなかった)ときに元のメモリがリークします。 こちらは今の僕の知識では解決出来ない気がしますが、 どうすれば解決するのでしょうか? お手数ですがお願いします。
- Wr5
- ベストアンサー率53% (2173/4061)
main()関数内の >//構造体ポインタの宣言 >stat *data; と、 status()関数の引数で受けているdataは別のモノですが、その辺りは理解されています? 関数内のローカル変数を書き換えても、呼び出し元に影響を与えることはできませんが。 # ついでに…realloc()の使い方に問題があります。 # 失敗した(拡張できなかった)ときに元のメモリがリークします。 # 今回の場合はプログラム終了しているので影響はほとんど無いでしょうが。
お礼
こちらで質問してから何度か挫折しそうになりましたが みなさんのおかげで解決することが出来ました。 ありがとうございました。 これからも質問することがあると思うのでまたよろしくお願いします。 (ここに出来上がったソースを貼り付けようとしたんですが、文字数が多い!と怒られました。)
補足
ありがとうございます。 >int func(int a*) { >*a = 10; >} >int main(void) { >int a = 0; >func(&a); >printf("%d", a); //10 >} >このコードは読めますよね? 大体分かるんですがint func(int a*)が分かりません。 a*というやり方は初めて見ました。それとなぜvoidでなくintなのかも 分かりません・・・。 >重要なのは、書き換えたい変数の型と、関数に渡す型の関係です。 >int型の変数を書き換えたいのでint*型の値を渡しています。 >一般化すれば、 >「T型の変数を呼び出し先の関数で書き換えたいとき、渡すべき値はT*型となる。」 >ということになります。 >そして今回はTがstat*なので、渡すべき型はstat**なのです。 >どういうポインタを使えばいいのか悩む必要はありません。型をみればすぐにわかります。 すごく分かりやすく説明してくれていて大変参考になりました。 ありがとうございます。