- ベストアンサー
malloc関数を使ってじゃんけんプログラムの履歴を表示する方法
- malloc関数を使ってじゃんけんプログラムの履歴を表示する方法について教えてください。
- 現在のプログラムでは、malloc関数を使用してユーザーとコンピュータが出した全ての手を履歴として表示しようとしていますが、うまくいかないようです。
- また、malloc関数を使わずに履歴表示をする方法もあるのでしょうか?
- みんなの回答 (5)
- 専門家の回答
質問者が選んだベストアンサー
>void rireki(void) >{ >a = (int *)malloc(sizeof(int) * (draw_no+lose_no+win_no)); >a[stage++] = comp; > >b = (int *)malloc(sizeof(int) * (draw_no+lose_no+win_no)); >b[stage++] = user; >} コールされるのはどのタイミングでしょう? そして、2回目以降にコールされた時、前回確保したaとbはどうなるでしょう。 # さらにその時のcompとuserには何が入っているでしょう? そして、rireki()がコールされるとstageがコール前より「2つ」増えています。 # 上記の場合だと、b[]への代入はバッファオーバーランしている可能性が高いです。 >hd[a[i]]という風に配列に配列を入れたのが駄目だったかもしれません。 1回目はa[0]には正しい履歴が入ります。 2回目にrireki()をコールした時に前回確保したaとbがメモリリーク(free()で開放する為のポインタが上書きにより失われる)し、a[]への登録がバッファオーバーラン(a[2]に書き込みしようとします。stageが2ですから)、a[0]とa[1]は「不定値」が残っています。 # malloc()は確保したメモリ内容に関してなにもしません。(calloc()なら0クリアしてくれますが…) 繰り返していって…… hd[a[0]]を参照した時に不定値になっていますので、高確率でhd[]の範囲外をアクセスしprintf()内で吹っ飛びます。 単純な方法だと… malloc()ではなくrealloc()で領域を「拡張」する。 以前記録した履歴(メモリ内容)は拡張後も保証されます。 stageの加算は履歴に「追加後」に行う必要があります。 ただし、realloc()は失敗する可能性もあるのでその辺りの対処が必要。 # a = (int *)realloc(a, sizeof(int) * (draw_no+lose_no+win_no)); # という使い方は正しくない。 メモリのフラグメントが発生しやすくなる。 realloc()を繰り返すのはコストが高い。 手間を掛けるのであれば、リスト構造体にして追加していく。 というのもありますな。 # 片方向リストで十分でしょう。
その他の回答 (4)
- Tacosan
- ベストアンサー率23% (3656/15482)
まずは「こうすればもっと (私の主観に基づいて) いいプログラムになる」という指摘: ・「双方の出した手」を構造体で覚えておくと楽. ・グー/チョキ/パーを表す数値や勝敗を表す数値はいわゆる「マジックナンバー」なので enum なり #define なりする. ・勝敗を決めるときに judge = (user - comp + 3) % 3 のような謎の式を直接書かない. 配列を参照するか, せめて関数にする. ・勝敗のカウンタは配列にして count_no を捨てる. メッセージも配列にすれば disp_result も消せる. ・再挑戦するかどうかを確認する関数がコメントでは confirm_result なのに実際の定義が confirm_retry なのはおかしい. そもそも「~関数の定義」などというコメントは百害あって一利なし. ・過去の手を覚えておく変数にはそれなりな名前を与える. 「ふつ~の人」が a とか b とかいう変数名を見て「これが過去の手を覚えているんだ」と思えるでしょうか? で realloc を使うとすれば, 「1回やるたびに realloc」はさすがにコストが馬鹿にならないので #2 で触れられているように「大きめの領域を確保しておき, 足りなくなったら realloc」の方が速度は稼げる. 連結リストにするかどうかは.... まあ今回の場合は好みだな. ランダムアクセスしたいなら連結リストは外すだろうけど, シーケンシャルアクセスしかしないっぽいので連結リストもないわけじゃない. あと恒例の突込み (苦笑) ですがメモリ確保系の関数で「0バイトを確保」したときに何が返るかは処理系依存>#3. NULL が返っても文句は言えないです.
お礼
アドバイスありがとうございます。 細かいところまで目が利くようになれるように頑張りたいです。 judge = (user - comp + 3) % 3は今やっている参考書からの引用です。 resultとretryは単純にタイプミスです。すいません。 ~関数の定義は、入門書の方で毎回書かれていたので一応書いてました。 aとかの変数名は適当になってました。
- Wr5
- ベストアンサー率53% (2173/4061)
>draw_no+lose_no+win_noの値に気付き、do~while文の後に >a = (int *)malloc(sizeof(int) * (draw_no+lose_no+win_no)); >b = (int *)malloc(sizeof(int) * (draw_no+lose_no+win_no)); ループ抜けた後では無意味です。 # それまでの履歴がないでしょう? ループ中でmalloc()すれば、最初と同じくメモリリークする上に以前のデータが引き継がれません。 別にmalloc()して、以前のデータを新しい領域にコピーして、以前の領域をfree()して……とかやるくらいならrealloc()を使えば済むハナシです。 実装によって異なりますが…realloc()で拡張する場合にやっているのは…… ・元のメモリブロックの後ろに要求された空きがあればメモリブロックを拡張して終わり。 ・空きがない場合は拡張後のサイズでメモリ確保し直して、元の領域のデータをコピーし、以前の領域はfree()。 ・拡張後のサイズでメモリ確保できない場合はなにもしないでNULLを返却し、失敗を通知。 って感じの処理を行っています。 # 縮小する場合は、たいてい成功するのでメモリブロック縮めて終わり。(溢れたデータについては保証しない) よって…勝負回数が不明である以上、malloc()1回で履歴を残す。というのは無理です。 # 大きい領域を確保すれば…にしてもそれを越えればアウトですし。(何度も書かれてますけど)
お礼
ありがとうございます。 realloc()でやってみようと思います。 reallocの動作の説明までしてもらいありがとうございます。
- Wr5
- ベストアンサー率53% (2173/4061)
>do~while文の前に >a = (int *)malloc(sizeof(int) * (draw_no+lose_no+win_no)); >b = (int *)malloc(sizeof(int) * (draw_no+lose_no+win_no)); メインループの前に(draw_no+lose_no+win_no)はいくつになりますか? >rireki関数の呼び出しはjyanken関数の後に宣言してみました。 「宣言してみました」は正しくないのですが… rireki()をコールすると、a[]とb[]への書き込みでヒープを破壊していきます。 勝負の回数が増えたところで、メインループの前にmalloc()で確保した領域が(サイズにsizeof(int) * (draw_no+lose_no+win_no)を指定しているから)自動的に増えて行くなんてコトはありません。 malloc()した時点での各変数の値を参照するだけですから、 その後でdraw_noやlose_no、win_noを増やしてもmalloc()で確保した領域のサイズは増えません。 メインループの前では(draw_no+lose_no+win_no)は0でしょうから、「サイズ0のメモリブロック」を確保しただけになります。 そこにデータを追加していけば待っているのは領域破壊(バッファオーバーラン)だけです。 固定的にサイズを取得しても、それを越えればやはりバッファオーバーランです。 >この場合履歴の表示まで実行できたのですが、グーなどの文字ではなく違う結果になってしまいました。 バッファオーバーランで破壊していますから、原理的には何が起こっても不思議ではない…です。 あと…この程度のプログラムならmalloc()等での動的確保を開放せずにプログラム終了しても問題ないでしょうが、free()でちゃんと開放するように習慣づけた方がいいでしょう。
補足
なんども入門書を読み直してるうちに自動的に増えるということに、勘違いしていたことに気付きました。 draw_no+lose_no+win_noの値に気付き、do~while文の後に a = (int *)malloc(sizeof(int) * (draw_no+lose_no+win_no)); b = (int *)malloc(sizeof(int) * (draw_no+lose_no+win_no)); としてみましたが、rireki関数には格納できないのでたぶんどこで宣言してもmalloc関数ではできないのでしょうか。 free()関数は終わりに書いてあります。 free(a); free(b); と記述しています。
- vector01
- ベストアンサー率50% (1/2)
こんにちは。業界でメインプログラマしている者です。 ソース拝見しました。rireki()を具体的にどこで呼んでいるのかが不明確ですが、恐らく1勝負毎ですね。 (do whileの最後辺り) 1回戦う毎に結果をメモリに残しておいて最後にprintfと言う事なんでしょうが、これは設計が分かれる所です。 大別すると、 1.初めに固定長バッファ(主さんの言う所の配列)を用意して決まった回数勝負する。 2.じゃんけんの回数を決めたくないから勝負する度にメモリ領域を確保してそこにしまっておく。 主さんの理想は2かと思います。 rireki()の呼び方が上述の通りと仮定すると、まずrireki()内でmallocしているのが問題です。 1勝負毎に別の空きメモリ空間に、その時の勝負回数分のサイズを別途用意してしまいます。 (1勝負する度にどんどんメモリリークする) この場合、2を実現するだけならmallocではなくreallocを使うのが現実的ですが、私はあまりお勧めしません。 (物によっては処理負荷が大きく、メモリの断片化も懸念されるので、業務の性質上、部下がやっていたらやめさせます) ただこの位の規模のじゃんけんゲームなら負荷も気にする必要は無いでしょうし、練習と言う意味では有りです。 reallocを使っての2の設計をもう少し細かくした別のアプローチとして、 ・1戦する度にreallocでは無く、予めmallocでN回分の領域を取っておいて、N回を超えたらもうN回分realloc と言う形でリアロケートの頻度を少なくする事で処理負荷と断片化を抑制する方法が考えられます。 実装効率、動的性と負荷を考えるとバランスはこう取るのが妥協ラインかと思います。後はNをどの位にするかでしょう。 もう少し進んでC++になるとVectorやlistにプッシュしていく。と言う技術をその内目にすると思います。 C++標準のSTLで実現する場合、内部ロジックはコンパイラによりますが、大体N回おきにreallocです。 reallocが嫌なら別途mallocした領域(Nより大きい)に今の内容をコピーして次の受け皿とする。 その後今まで使っていた領域をfreeで解放する。 とも出来ますが、結局断片化等の理由でアロケートサイズが足りなければmallocは失敗します。 私がもし、業務でやるならこうしますと言う案で言うと、 メモリアロケータやlistから自作出来るのであれば、list内部にアロケート機構を一切絡ませずに動的生成部分は外に出す。 その後、じゃんけんの度に勝負1回分をmallocしてlistに繋いでいく。と言う方法が取れます。 このじゃんけんの結果を格納するメモリをページ分けしておけば、その部分の断片化は起こらなく出来ます。 その為メモリをかなり有効に活用でき、延々と履歴を取れる様なイメージには近づけられると思います。 ただメモリは有限なので、どんなに頑張ってもNの回数を無限には出来ません。 無限にしたいならキュー構造等を使って規定回数を超えたら古い物から削除して新しい物を上書き。 とする事で妥協する位です。 少し話が難しくなったかも知れませんが、まずはreallocを使って実装してみるのが良いのでは無いでしょうか。 それが出来る事で、reallocの問題点を理解・認識して次に進めれば良いと思います。
お礼
reallocについては入門書には全く触れられていなかったので今の段階ではよくわかりませんが、今やっている本に載っているみたいなのでこれからみてみようと思います。 ありがとうございます。
お礼
mallocではなくreallocというものを使ってやってみれば良いんですね。 勘違いしていました。ありがとうございます。
補足
回答ありがとうございます。 言われてみると酷い間違えばっかでした。 do~while文の前に a = (int *)malloc(sizeof(int) * (draw_no+lose_no+win_no)); b = (int *)malloc(sizeof(int) * (draw_no+lose_no+win_no)); rireki関数の呼び出しはjyanken関数の後に宣言してみました。 /* rireki関数 */ void rireki(void) { a[stage] = comp; b[stage] = user; stage++; } この場合履歴の表示まで実行できたのですが、グーなどの文字ではなく違う結果になってしまいました。