ページ 11

セーブとロードについて

Posted: 2011年7月18日(月) 12:22
by maku02
初めまして
主にC++主体でRPGを製作しようとしています
先に書いておくと、以下のプログラムを見てもらえればわかるとは思いますが、C言語及びC++の知識は俄かもいいところです
それで、今はセーブとロードの機能を実装しようとして、fstreamを使って試しにキャラクターの持つ変数をセーブ&ロードしてみようとしたのですが、セーブ時には何のエラーも起きないのに、その後でそのセーブデータをロードすると、プログラム終了時に下記のエラーが出ます(一部こちらで勝手に不要と判断した部分は省略しています)


Debug Assertion Failed!

Program:...ments...(略)....exe
File:f:¥dd¥vctools...(略)...¥dbgdel.cpp
Line:52

Expression:_BLOCK_TYPE_IS_VALID(pHead->nBlockUse)

For information on how your program can cause an assertion failure, see the Visul C++ documentation on asserts.


以上がエラー内容です

調べた結果、どうやらメモリ領域の確保と解放に関連するエラーらしいのですが自分にはどこが原因なのかが分かりません
ですので、以下、(自分の憶測で問題はここら辺に判断した)プログラムを記載します

コード:

//キャラクタークラスの持つ変数
class Character
{
	string name;

	int chara_x;//キャラのx座標
	int chara_y;//キャラのy座標
	int chara_m_x[128];//キャラのマップ上におけるx座標
	int chara_m_y[128];//キャラのマップ上におけるy座標

	int syoku;	//職業No.
	string type;//職業名
	int syokulv;//職業レベル
	int ss;		//筋力職ボーナス
	int vs;		//体力職ボーナス
	int ds;		//命中職ボーナス
	int as;		//敏捷職ボーナス
	int ws;		//知能職ボーナス
	int ls;		//幸運職ボーナス
	int hs;		//HP職ボーナス
	int ms;		//MP職ボーナス
	int syokuexp;	//職業経験値

	int bonus;	//能力値ボーナス
	int sb;		//筋力ボーナス
	int vb;		//体力ボーナス
	int db;		//命中ボーナス
	int ab;		//敏捷ボーナス
	int wb;		//知能ボーナス
	int lb;		//幸運ボーナス
	int hb;		//HPボーナス
	int mb;		//MPボーナス

	int lv;		//レベル
	int str;	//筋力
	int vit;	//体力
	int dex;	//命中
	int agi;	//敏捷
	int wis;	//知能
	int luc;	//幸運
	int hp;		//HP
	int mp;		//MP
	int weight;	//重さ
	int exp;	//経験値

	//Character::sakusei()に使う変数
	int sakusei_flag;
	int m_x;
	int m_xs[10];
	int m_y;
	int m_n;
	int m_i;
	int m_tf;
	string temp;
	string temps;
	char c[11];

public:
//以下諸々の関数
}

//セーブデータクラスのセーブ関数(セーブデータクラスは他には特に変数も関数も持っていないので省略)
void Savedata::save()
{
	ofstream ofs;
	ofs.open("SAVEDATA.txt",ios_base::out | ios_base::trunc | ios_base::binary);
	ofs.write((char*)pchara,sizeof(Character)); //Character *pchara =  &chara(=クラスキャラクターのオブジェクトのポインタ)
	ofs.close();
}

//ロードデータクラスのロード関数(ロードデータクラスは他には特に変数も関数も持っていないので省略)
void Loaddata::load()
{
	ifstream ifs;
	ifs.open("SAVEDATA.txt",ios_base::out | ios_base::app | ios_base::binary);
	ifs.read((char*)pchara,sizeof(Character));
	ifs.close();
}
取り敢えずこれだけ書いてみましたが、他にも書くべきことがあったらそれも教えてください
それと、こちらの環境はVC2010Expressです
質問内容は、
1、上記エラーの原因及び解決方法
2、そもそもクラスの持つ変数の入出力はこのやり方でいいのか
です

よろしくお願いします

Re: セーブとロードについて

Posted: 2011年7月18日(月) 12:31
by softya(ソフト屋)
少なくともstringクラスを単にバイナリとしてセーブ・ロードしているのはマズイです。ロードしたときに再現されません。

[対処]
stringから文字列を取り出して可変長のデータとして書きだしてください。サイズと文字列実体をファイルに書き出します。
読み込むときは一時的に確保したバッファに読み込んで、そこからstringに代入します。
なので、同じ構造体にせず分けた方が良いのではないでしょうか。

Re: セーブとロードについて

Posted: 2011年7月18日(月) 13:40
by maku02
softya(ソフト屋) さん回答ありがとうございます

つまり、少なくとも今回のプログラムにおいては
string name;
string type;
string temp;
string temps;
の四つが、string型という大きさの決まっていない変数であり、それが原因ということなのでしょうか

それとすみません、可変長のデータとして云々がどういうことか自分にはよくわからないのですが、
例えば、上記四つのstring型変数をchar型変数に代入して、そのchar型変数をバイナリファイルに入出力すれば問題無いのでしょうか

厚かましいのを承知でお願いしますが、出来ればその辺りの具体的なプログラムの書き方も教えていただけないでしょうか

Re: セーブとロードについて

Posted: 2011年7月18日(月) 13:46
by maku02
追記です
>>tringから文字列を取り出して可変長のデータとして書きだしてください。サイズと文字列実体をファイルに書き出します。

とのことですが、
例えば、string nameだけをファイル保存するとして

コード:

ofs.open("SAVEDATA.txt",ios_base::out | ios_base::trunc | ios_base::binary);
ofs.write((char*)&name,sizeof(name));
みたいに書けばいいのでしょうか

Re: セーブとロードについて

Posted: 2011年7月18日(月) 13:55
by naohiro19
stringクラスの文字列を取り出すときは

コード:

#include <string>
#include <iostream>
int main(void)
{
    std::string str="Test string";
    std::cout << str.c_str() << std::endl;
   return 0;
}
とする必要があります。(上記はコンソール版です)

Re: セーブとロードについて

Posted: 2011年7月18日(月) 14:41
by softya(ソフト屋)
stringはポインタを含むクラスですから、そのままメンバ情報をバイナリとして書きだしてもロードで再現することは出来ません。
なので一旦文字列として書きだす必要があるのですが、その場合はバイナリファイルの構造を検討する必要があります。

バイナリ上は
[構造体]
[文字列長さ][文字列]
[文字列長さ][文字列]
[文字列長さ][文字列]
[文字列長さ][文字列]
の形で書きだしてやる必要があります。

ひとつの文字列で、
size_type size = name.size;
ofs.write(&size,sizeof(size));
ofs.write(name.c_str(),size);
って感じでしょうか。
どうなるかは出来上がったファイルをバイナリエディタで確認してみてください。

Re: セーブとロードについて

Posted: 2011年7月18日(月) 15:26
by maku02
御両名とも返信ありがとうございます

早速softyaさんの言う

コード:

size_type size = name.size;
ofs.write(&size,sizeof(size));
ofs.write(name.c_str(),size);
を書いてみたのですが、
まずsize_type size = name.size;で識別子size_typeが定義されていないと言うので、

コード:

string::size_type size = name.size;//string::を加えた
ofs.write(&size,sizeof(size));
ofs.write(name.c_str(),size);
と書きなおすと今度は、name.sizeの部分にバインドされた関数へのポインターは関数の呼び出しにのみ使用出来ます
とのエラー

コード:

string::size_type size = name.size();//()を加えた
ofs.write(&size,sizeof(size));
ofs.write(name.c_str(),size);
と書きなおしましたが、もう一つ、二行目の&sizeに「型"size_t"の引数は型"const char *"のパラメーターと互換性がありません」と出ましたので

コード:

string::size_type size = name.size();
ofs.write((char*)&size,sizeof(size));//(char*)を加えた
ofs.write(name.c_str(),size);
としたところでようやくコンパイルできましたが、
name = "maku"
としてsaveしたらファイルには「 maku」と、ファイルサイズの表示は空白になっていました
これはsoftyaさんの意図したプログラムになっているのでしょうか

Re: セーブとロードについて

Posted: 2011年7月18日(月) 15:29
by softya(ソフト屋)
テストしてなかったのはごめんなさい。
それで、バイナリエディタで確認されたんですよね?テキストエディタではなく。

「Stirlingの詳細情報 : Vector ソフトを探す!」
http://www.vector.co.jp/soft/win95/util/se079072.html

Re: セーブとロードについて

Posted: 2011年7月18日(月) 15:47
by maku02
失礼しました
テキストエディタで開いていました

binで保存してバイナリエディタで確認してみたところ、
「03 00 00 00 41 41 41」(name = "AAA")
と表示されていました
これは初めの03がnameの中身が3バイトであるという意味で良いんですよね?

ところで保存はそれでいいとして、読みこむ時はどうすればいいのでしょう

コード:

void Character::load()
{
	ifstream ifs;
	ifs.open("SAVEDATA.bin",ios_base::out | ios_base::app | ios_base::binary);
	string::size_type size = name.size();
	ifs.read((char*)&size,sizeof(size));
	ifs.read((char*)&name,size);
	ifs.close();
}
という風にやってみましたが案の定プログラムがエラーで落ちました

Re: セーブとロードについて

Posted: 2011年7月18日(月) 15:54
by softya(ソフト屋)
>「03 00 00 00 41 41 41」(name = "AAA")
リトルエンディアンですから合っています。

読み込み方。
(1)まず文字列サイズだけを読みこみます。
(2)読み込まれたサイズの値+1でnewでchar配列を確保します。
(3)確保したchar配列に文字列を読み込みます。
(4)char配列の終端には'\0'を書きこんでください。
(5)このchar配列をstringに代入してください。
(6)最後にchar配列をdeleteします。
これをひとつのprivateメンバ関数にした方が良いと思います。

Re: セーブとロードについて

Posted: 2011年7月18日(月) 16:12
by maku02
softyaさんの言う手順をなぞって作ってはみましたが、やはりどこかがおかしいようでエラーです

コード:

void Character::load()
{
	ifstream ifs;
	ifs.open("SAVEDATA.bin",ios_base::out | ios_base::app | ios_base::binary);
	string::size_type size = name.size();
	ifs.read((char*)&size,sizeof(size));
	char* buf = new char[size + 1];
	ifs.read(buf,size);
	buf[size + 1] = '\0';
	name = buf;
	delete buf;
	ifs.close();
}
以下エラー内容
HEAP CORRUPTION DETECTED: after Normal block(#106)at0x027C18E8.
CRT detected that the application wrote to memory after end of heap buffer.

プログラムを書いていて特にわからなかったのはファイル読み込み時にちゃんとサイズの部分を読みこめているのか、及びその後に続く名前の文字列のみを読みこめているのか、です

Re: セーブとロードについて

Posted: 2011年7月18日(月) 16:21
by softya(ソフト屋)
maku02 さんが書きました:プログラムを書いていて特にわからなかったのはファイル読み込み時にちゃんとサイズの部分を読みこめているのか、及びその後に続く名前の文字列のみを読みこめているのか、です
それはデバッガを使って確認して下さい。

それと
buf[size + 1] = '\0';
はバッファの範囲外に書きこんできます。
配列の添字と宣言時のサイズの関係を思い出してみてください。

あと
delete buf;
ではなく
delete [] buf;
だと思います。

Re: セーブとロードについて

Posted: 2011年7月18日(月) 16:46
by maku02

コード:

void Character::load()
{
	ifstream ifs;
	ifs.open("SAVEDATA.bin",ios_base::out | ios_base::app | ios_base::binary);
	string::size_type size = name.size();
	ifs.read((char*)&size,sizeof(size));
	char* buf = new char[size + 1];
	ifs.read(buf,size);
	buf[size] = '\0';
	name = buf;
	delete [] buf;
	ifs.close();
}
と書いて晴れてようやくエラーが出なくなったのは良いのですが、nameを表示する関数を入れて確認してみたところ、
どうもnameに意図した文字列が入っていないようで、空白しか表示されませんでした
これは何故なんでしょうか

Re: セーブとロードについて

Posted: 2011年7月18日(月) 16:55
by softya(ソフト屋)
デバッガでnameに代入された値の確認はされたのでしょうか?

Re: セーブとロードについて

Posted: 2011年7月18日(月) 17:35
by maku02
デバッガというものが何なのかよくわからなかったので避けていたのですが、なんとか使えました

bufに関してはload関数の中で以下のような変遷をしていました

0xcccccccc <不適切な Ptr>
0x00e212d0"ヘヘヘヘヘヘヘヘヘヘヘヘォォォォォォォォ"
0x00e212d0"・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・ウテッヨ"

私には意味が良くわかりませんでしたが


それはともかくとして、先程、空白が表示されたと言ったのは、こちらの単純な初期化ミスでした
デバッガでもちゃんとnameには値が入っていましたし、無事にstringにおけるデータの入出力が出来ました

取り敢えず、後はそれぞれの変数を同じファイルに入出力していくという形でやっていってみようと思います
もしもまた詰まってしまったらその時はここに来てしまうとは思いますが、今回のところはこれにて解決とさせていただきます
特にsoftyaさんには長々とこのような質問に根気よくお付き合いしていただき、本当にありがとうございます

Re: セーブとロードについて

Posted: 2011年7月18日(月) 17:42
by softya(ソフト屋)
無事動いて何よりです。
bufに関してはread後なら値が入っているはずです。
デバッガをうまく使えばデバッグ効率はアップしますので、うまく利用してください。

あと投稿ボタンの隣にある解決チェックを入れてくださいね。

Re: セーブとロードについて

Posted: 2011年7月18日(月) 17:44
by maku02
忘れていました、もうしわけないです
本当にありがとうございました
今度こそ解決とさせていただきます