【C++】ファイル読み書きについてわかる方お願いします【fstream】

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
karaage

【C++】ファイル読み書きについてわかる方お願いします【fstream】

#1

投稿記事 by karaage » 10年前

C++を勉強しながらゲームを作っています。
自己解決はしたのですが、どうしても納得いかないことがあるのでわかる方説明お願いします。

ゲームスコアの記録、表示のためにファイル入出力のプログラムを作成したのですが、以下のコードで
test□□□□ cェ
100
といったような出力がされます

コード:

#include <fstream>
#include <iostream>
using namespace std;

/* ファイル読み書きテスト */
int main(){
	fstream out("testFile.txt",fstream::out, fstream::binary);
	/* 書き込みテスト */
	char* testStr_o = "test";
	int testInt_o = 100;
	out.write( testStr_o,sizeof(testStr_o) );
	out.write( reinterpret_cast< char* >(&testInt_o), sizeof(int) );

	out.close();
 
	/* 読み込みテスト */
	fstream in("testFile.txt",fstream::in, fstream::binary);
	char* testStr_i = new char[4];
	int testInt_i;
	in.read(testStr_i,sizeof(testStr_i));
	in.read(reinterpret_cast< char* >(&testInt_i),sizeof(int));

	cout << testStr_i << endl;
	cout << testInt_i;

	in.close();
}
これを次のコードのように改善したところ
test
100
と表示され、問題はなくなりました。
(前のコードとの違いは18,21行です)

コード:

#include <fstream>
#include <iostream>
using namespace std;

/* ファイル書き込みテスト */
int main(){
	fstream out("testFile.txt",fstream::out, fstream::binary);
	/* 書き込みテスト用 */
	char* testStr_o = "test";
	int testInt_o = 100;
	out.write( testStr_o,sizeof(testStr_o) );
	out.write( reinterpret_cast< char* >(&testInt_o), sizeof(int) );

	out.close();

	/* 読み込みテスト用 */
	fstream in("testFile.txt",fstream::in, fstream::binary);
	char* testStr_i = new char[5];
	int testInt_i;
	in.read(testStr_i,sizeof(testStr_i));
	testStr_i[4] = '\0';
	in.read(reinterpret_cast< char* >(&testInt_i),sizeof(int));

	cout << testStr_i << endl;
	cout << testInt_i;

	in.close();
}
このコードに関して、readでsizeofをとる場合null文字を入れるために領域を追加した分余計に読み込んでしまわないのか、
いままで考えて来なかったのですがそもそも何故char*終端にnull文字を入れないと化けるのか、という2点が納得いかないので、
このことについてわかる方教えていただきたいです。長くなってしまいましたが以上です。

karaage

Re: 【C++】ファイル読み書きについてわかる方お願いします【fstream】

#2

投稿記事 by karaage » 10年前

すみません追記です
ideはフリー版のvc++2010を使ってます

アバター
みけCAT
記事: 6734
登録日時: 15年前
住所: 千葉県
連絡を取る:

Re: 【C++】ファイル読み書きについてわかる方お願いします【fstream】

#3

投稿記事 by みけCAT » 10年前

karaage さんが書きました:これを次のコードのように改善したところ
test
100
と表示され、問題はなくなりました。
このコードは環境依存であり、不自然です。
たまたま書き込むバイト数とポインタのバイト数が一致したため、問題が隠れています。
試しに"test"ではなく"testtesttest"を読み書きしようとしてみてください。
karaage さんが書きました:このコードに関して、readでsizeofをとる場合null文字を入れるために領域を追加した分余計に読み込んでしまわないのか、
sizeof(testStr_i)、すなわちポインタのサイズは領域を追加しても変わりません。従って、「領域を追加した分余計に読み込んでしまう」という現象は発生しません。
karaage さんが書きました:いままで考えて来なかったのですがそもそも何故char*終端にnull文字を入れないと化けるのか、
null文字を入れないと出力プログラムはどこが文字列の終わりなのかわからず、
値が不定である、アクセスしてはいけない場所にアクセスしているからです。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

karaage

Re: 【C++】ファイル読み書きについてわかる方お願いします【fstream】

#4

投稿記事 by karaage » 10年前

みけCAT さんが書きました: このコードは環境依存であり、不自然です。
たまたま書き込むバイト数とポインタのバイト数が一致したため、問題が隠れています。
試しに"test"ではなく"testtesttest"を読み書きしようとしてみてください。
みけCAT さんが書きました: sizeof(testStr_i)、すなわちポインタのサイズは領域を追加しても変わりません。従って、「領域を追加した分余計に読み込んでしまう」という現象は発生しません。
確かにポインタのサイズが偶然合っていたようですね、試しにchar[5]とchar[10]のサイズをsizeofを出力させて確認したところ、どちらとも4を出力しました。コードの読み書き部分を以下のように改善したのですがこちらは問題ないでしょうか?少なくとも"test"と"testtest"で確認したら問題は無いような挙動をしました。

コード:

	const int lengthOfChar = 4;  //読み書きする文字数
	/* 書き込みテスト用 */
	char* testStr_o = "test";
	for (int i = 0; i < lengthOfChar; i++) {
		out.write( &testStr_o[i],sizeof(char) );
	}

コード:

	char* testStr_i = new char[lengthOfChar];
	for (int i = 0; i < lengthOfChar; i++ ) {
		in.read( &testStr_i[i],sizeof(char));
	}
みけCAT さんが書きました: null文字を入れないと出力プログラムはどこが文字列の終わりなのかわからず、
値が不定である、アクセスしてはいけない場所にアクセスしているからです。
終端としてnull文字が無いといけない理由に疑問に思ったきっかけとして、例えば
char* testStr = "test";
と宣言してcoutで出力するのは問題が無いのに
char* testStr = new char[4];
として、後でリソースから"test"と読み込んでcoutで出力したときは文字化けした、ということが背景としてありました。
これは前者の宣言では実際にtestStrに入っているものは"test"ではなく"test\0"が入っているという認識で間違いないでしょうか?
少なくともwhileで'\0'まで出力させたら問題はありませんでした。検討違いであればご指摘お願いします。

アバター
みけCAT
記事: 6734
登録日時: 15年前
住所: 千葉県
連絡を取る:

Re: 【C++】ファイル読み書きについてわかる方お願いします【fstream】

#5

投稿記事 by みけCAT » 10年前

karaage さんが書きました:試しにchar[5]とchar[10]のサイズをsizeofを出力させて確認したところ、どちらとも4を出力しました。
本当ですか?それはコンパイラの不都合かもしれません。

コード:

#include <cstdio>

int main(void) {
	printf("%u\n", (unsigned int) sizeof(char[5]));
	printf("%u\n", (unsigned int) sizeof(char[10]));
	return 0;
}
http://melpon.org/wandbox/permlink/gmaBVnn0faw8z4Pn
karaage さんが書きました:コードの読み書き部分を以下のように改善したのですがこちらは問題ないでしょうか?少なくとも"test"と"testtest"で確認したら問題は無いような挙動をしました。

コード:

	const int lengthOfChar = 4;  //読み書きする文字数
	/* 書き込みテスト用 */
	char* testStr_o = "test";
	for (int i = 0; i < lengthOfChar; i++) {
		out.write( &testStr_o[i],sizeof(char) );
	}

コード:

	char* testStr_i = new char[lengthOfChar];
	for (int i = 0; i < lengthOfChar; i++ ) {
		in.read( &testStr_i[i],sizeof(char));
	}
読み込み時にヌル文字を格納していない、もしくはヌル文字を格納する領域を確保していない(→ヌル文字を範囲外に格納している疑いがある)のが気になります。
あとは致命的な問題は無さそうだと思います。
オフトピック
文字列リテラルへのポインタを格納するので、testStr_oの型はchar*よりconst char*の方がいいでしょう。
karaage さんが書きました:終端としてnull文字が無いといけない理由に疑問に思ったきっかけとして、例えば
char* testStr = "test";
と宣言してcoutで出力するのは問題が無いのに
char* testStr = new char[4];
として、後でリソースから"test"と読み込んでcoutで出力したときは文字化けした、ということが背景としてありました。
これは前者の宣言では実際にtestStrに入っているものは"test"ではなく"test\0"が入っているという認識で間違いないでしょうか?
"test"も"test\0"もtestStrには入っていません。
testStrに入っているのは"test\0"の「場所」(先頭へのポインタ)です。
N3337 2.14.5 String literals さんが書きました:14 After any necessary concatenation, in translation phase 7 (2.2), ’\0’ is appended to every string literal so
that programs that scan a string can find its end.
(http://www.open-std.org/jtc1/sc22/wg21/ ... /n3337.pdf)
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

karaage

Re: 【C++】ファイル読み書きについてわかる方お願いします【fstream】

#6

投稿記事 by karaage » 10年前

みけCAT さんが書きました:
karaage さんが書きました:試しにchar[5]とchar[10]のサイズをsizeofを出力させて確認したところ、どちらとも4を出力しました。
本当ですか?それはコンパイラの不都合かもしれません。

コード:

#include <cstdio>

int main(void) {
	printf("%u\n", (unsigned int) sizeof(char[5]));
	printf("%u\n", (unsigned int) sizeof(char[10]));
	return 0;
}
http://melpon.org/wandbox/permlink/gmaBVnn0faw8z4Pn
説明不足でしたすみません...
領域の違うポインタのサイズを確認した場合にどちらとも4を出力しました。

コード:

	char* test1 = new char[5];
	char* test2 = new char[10];

	cout << sizeof(&test1) << endl;
	cout << sizeof(&test2) << endl;
みけCAT さんが書きました:
karaage さんが書きました:コードの読み書き部分を以下のように改善したのですがこちらは問題ないでしょうか?少なくとも"test"と"testtest"で確認したら問題は無いような挙動をしました。

コード:

	const int lengthOfChar = 4;  //読み書きする文字数
	/* 書き込みテスト用 */
	char* testStr_o = "test";
	for (int i = 0; i < lengthOfChar; i++) {
		out.write( &testStr_o[i],sizeof(char) );
	}

コード:

	char* testStr_i = new char[lengthOfChar];
	for (int i = 0; i < lengthOfChar; i++ ) {
		in.read( &testStr_i[i],sizeof(char));
	}
読み込み時にヌル文字を格納していない、もしくはヌル文字を格納する領域を確保していない(→ヌル文字を範囲外に格納している疑いがある)のが気になります。
あとは致命的な問題は無さそうだと思います。
オフトピック
文字列リテラルへのポインタを格納するので、testStr_oの型はchar*よりconst char*の方がいいでしょう。
確かにnull文字の入れ忘れがありますね、指摘されるまで気が付きませんでした。
これでどうでしょう。

コード:

	char* testStr_i = new char[lengthOfChar + 1];
	for (int i = 0; i < lengthOfChar; i++ ) {
		in.read( &testStr_i[i],sizeof(char));
	}
	testStr_i[lengthOfChar] = '\0';
みけCAT さんが書きました:
karaage さんが書きました:終端としてnull文字が無いといけない理由に疑問に思ったきっかけとして、例えば
char* testStr = "test";
と宣言してcoutで出力するのは問題が無いのに
char* testStr = new char[4];
として、後でリソースから"test"と読み込んでcoutで出力したときは文字化けした、ということが背景としてありました。
これは前者の宣言では実際にtestStrに入っているものは"test"ではなく"test\0"が入っているという認識で間違いないでしょうか?
"test"も"test\0"もtestStrには入っていません。
testStrに入っているのは"test\0"の「場所」(先頭へのポインタ)です。
N3337 2.14.5 String literals さんが書きました:14 After any necessary concatenation, in translation phase 7 (2.2), ’\0’ is appended to every string literal so
that programs that scan a string can find its end.
(http://www.open-std.org/jtc1/sc22/wg21/ ... /n3337.pdf)
ポインタを理解していないことを晒してしまったようでお恥ずかしい...
char*で宣言した場合には参照場所の先頭ポインタを指している、そのため領域確保する際に
char* testStr = new char[5];
とした場合と、
char* testStr = new char[10];
とした場合とでtestStrのサイズが同じなのはtestStrにcharの配列が入っているわけではなく、
testStrには確保した領域の先頭ポインタを指しているから。という認識で合ってますでしょうか。
何度も申し訳ないですが、回答よろしくお願いします

アバター
みけCAT
記事: 6734
登録日時: 15年前
住所: 千葉県
連絡を取る:

Re: 【C++】ファイル読み書きについてわかる方お願いします【fstream】

#7

投稿記事 by みけCAT » 10年前

karaage さんが書きました:説明不足でしたすみません...
領域の違うポインタのサイズを確認した場合にどちらとも4を出力しました。

コード:

	char* test1 = new char[5];
	char* test2 = new char[10];

	cout << sizeof(&test1) << endl;
	cout << sizeof(&test2) << endl;
それはnew[]した領域とは関係ない、char*型のデータを指すポインタのサイズです。
char*型のサイズを取得するには、素直に

コード:

#include <iostream>
using std::cout;
using std::endl;

int main(void) {

	char* test1 = new char[5];
	char* test2 = new char[10];

	cout << sizeof(test1) << endl;
	cout << sizeof(test2) << endl;

	delete[] test1;
	delete[] test2;
	return 0;
}
とすればいいでしょう。
karaage さんが書きました:確かにnull文字の入れ忘れがありますね、指摘されるまで気が付きませんでした。
これでどうでしょう。

コード:

	char* testStr_i = new char[lengthOfChar + 1];
	for (int i = 0; i < lengthOfChar; i++ ) {
		in.read( &testStr_i[i],sizeof(char));
	}
	testStr_i[lengthOfChar] = '\0';
悪くないと思います。
lengthOfCharバイト一気に読み込ませてもいいでしょう。

コード:

	char* testStr_i = new char[lengthOfChar + 1];
	in.read( &testStr_i[i],sizeof(char) * lengthOfChar);
	testStr_i[lengthOfChar] = '\0';
※テストしていません
karaage さんが書きました:ポインタを理解していないことを晒してしまったようでお恥ずかしい...
char*で宣言した場合には参照場所の先頭ポインタを指している、そのため領域確保する際に
char* testStr = new char[5];
とした場合と、
char* testStr = new char[10];
とした場合とでtestStrのサイズが同じなのはtestStrにcharの配列が入っているわけではなく、
testStrには確保した領域の先頭ポインタを指しているから。という認識で合ってますでしょうか。
合ってると思います。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

karaage

Re: 【C++】ファイル読み書きについてわかる方お願いします【fstream】

#8

投稿記事 by karaage » 10年前

読み込む文字数がわかっているならループをさせずとも読み込めますね、参考にさせていただきます。
これを機にポインタについて理解しようと努力してみます。みけCATさんありがとうございました。

閉鎖

“C言語何でも質問掲示板” へ戻る