C++のファイルストリームに関して

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

C++のファイルストリームに関して

#1

投稿記事 by 赤鬼 » 13年前

バイナリファイルで書き込む際にコンストラクタとデストラクタでしっかり開放してくれるらしいので便利だと思うのですが何故第一引数が(char*)なのですか?。
(例

コード:

#include <fstream>
#include <iostream>
using namespace std;
void main(){
	int a=10;
	cout<<a<<endl;//確認用出力
	ofstream ofs("a.txt",ios::out|ios::binary);
	if (!ofs){
		cout << "ファイル file.txt が開けません";
		return;
	}
	ofs.write((char*)&a,1);
	ofs.close();
	a=0;
	cout<<a<<endl;//確認用出力
	ifstream ifs("a.txt",ios::out|ios::binary);
	ifs.read((char*)&a,1);
	cout<<a<<endl;//確認用出力
}
Cではfwrite(const void *buf, size_t size, size_t n, FILE *fp)
だったので(指すアドレス(アドレスはどれも4byteのはずですから(但し32bit)),何の型か(何byteか),要素数,ファイルポインタ)
と分かりやすかったのですが、ファイルストリームでは単位ごとのサイズ指定が無くなっています。
またポインタの仕組みさえ分かればこういうことも出来ますから、fwrite内部で似たような事をしてると思います。

コード:

void main(){
	int a[2]={48,49};
	char *p=a;
	printf(p);
	putchar('\n');
	p+=sizeof(int);//4でも可(int 型の変数は4バイトなので)
	printf(p);
}
だとしたら、型のサイズも取らないで内部でどういった処理をしているのか気になりました。

仕様だからとかそういうのはなしでお願いします。
何故char*なのかの理由が知りたいです。

かずま

Re: C++のファイルストリームに関して

#2

投稿記事 by かずま » 13年前

赤鬼 さんが書きました:仕様だからとかそういうのはなしでお願いします。
何故char*なのかの理由が知りたいです。
typedef basic_ofstream<char> ofstream;
typedef basic_ifstream<char> ifstream;

と宣言されているからです。

int を入出力したいのだったら、次のようにしましょう。

コード:

#include <fstream>
#include <iostream>

using namespace std;

int main()
{
    int a[3] = { 10, 11, 12 };
    for (int i = 0; i < 3; i++) cout << a[i] << endl;  //確認用出力
    basic_ofstream<int> ofs("a.bin", ios::out|ios::binary);
    if (!ofs){
        cout << "ファイル a.bin が開けません";
        return 0;
    }
    ofs.write(a, 3);
    ofs.close();
    for (int i = 0; i < 3; i++) a[i] = 0;
    for (int i = 0; i < 3; i++) cout << a[i] << endl;  //確認用出力
    basic_ifstream<int> ifs("a.bin", ios::in|ios::binary);
    ifs.read(a, 3);
    for (int i = 0; i < 3; i++) cout << a[i] << endl;  //確認用出力
}

Ryo

Re: C++のファイルストリームに関して

#3

投稿記事 by Ryo » 13年前

int a=10;
a=10 だから偶然成功しているように見えるだけです。
負の値や256以上の値で試すと、望んだ結果にはならないでしょう

ISLe
記事: 2650
登録日時: 15年前
連絡を取る:

Re: C++のファイルストリームに関して

#4

投稿記事 by ISLe » 13年前

赤鬼 さんが書きました:と分かりやすかったのですが、ファイルストリームでは単位ごとのサイズ指定が無くなっています。
そんなことはありませんよ。

コード:

#include <fstream>
#include <iostream>
using namespace std;
void main(){
    int a=10;
    cout<<a<<endl;//確認用出力
    ofstream ofs("a.txt",ios::out|ios::binary);
    if (!ofs){
        cout << "ファイル file.txt が開けません";
        return;
    }
    ofs.write((char*)&a,sizeof(int)*1); // ←サイズを指定
    ofs.close();
    a=0;
    cout<<a<<endl;//確認用出力
    ifstream ifs("a.txt",ios::out|ios::binary);
    ifs.read((char*)&a,sizeof(int)*1); // ←サイズを指定
    cout<<a<<endl;//確認用出力
}
赤鬼 さんが書きました:仕様だからとかそういうのはなしでお願いします。
何故char*なのかの理由が知りたいです。
仕様抜きには説明できないことなんですけどね。
オブジェクトの内部表現を扱えるのはcharなのでそれを指すためのchar*かと思います。

どうせ内部ではchar*でアクセスしないといけないので手間を省いたのかと思います。
赤鬼 さんが書きました:またポインタの仕組みさえ分かればこういうことも出来ますから、fwrite内部で似たような事をしてると思います。

コード:

void main(){
	int a[2]={48,49};
	char *p=a;
	printf(p);
	putchar('\n');
	p+=sizeof(int);//4でも可(int 型の変数は4バイトなので)
	printf(p);
}
ご自身でchar*のpを使ってますね。
#それ以外は滅茶苦茶ですけど。
char*じゃないとちゃんと動かないのを知っていたのでは?

赤鬼

Re: C++のファイルストリームに関して

#5

投稿記事 by 赤鬼 » 13年前

かずまさん
回答有難うございます。
なるほどchar型に型指定されたテンプレートクラスだったんですね。
これならすでに指定されているわけですから、安心して使えます。

ちなみにクラスや構造体、コンテナ等は使用出来ませんでした(普通、使わないだろうけど)。
fwrite()では構造体,クラスを入れても期待した動きをしました(普通やりませんけど、コンテナは×、テンプレートクラスはちゃんとした値が帰ってきました)。
ただ、単に偶然上手く出来ただけかもしれませんが(なんか怖いですね)。


ryoさん
回答有難うございます。

はい、そのとおりでした。
あれで扱えるのはucharの範囲みたいですね。
理由が良く分からないのでよろしかったら説明を頂けませんか?

ISLeさん
回答して頂き有難うございます。
ISLe さんが書きました: ofs.write((char*)&a,sizeof(int)*1); // ←サイズを指定
私が見ているリファレンスでは
write( const char *バッファ, streamsize 文字数 );
こうなっています。
文字数では無いのでしょうか?
ISLe さんが書きました: 仕様抜きには説明できないことなんですけどね。
あの、そういう意味で言ったのではありません。
別の質問で”仕様だから”とだけ言われたことがあり、それじゃ説明になってなかったので同じ事やられたら嫌だなと思いまして。
ISLe さんが書きました: オブジェクトの内部表現を扱えるのはcharなのでそれを指すためのchar*かと思います。
そうなんですか、勉強不足でした。
ところでオブジェクトの内部表現とはなんでしょうか?
調べても出てこないし、良く分かりません。
ISLe さんが書きました: ご自身でchar*のpを使ってますね。
#それ以外は滅茶苦茶ですけど。
char*じゃないとちゃんと動かないのを知っていたのでは?
まあ、滅茶苦茶ですよね、普通こんなことしませんし、やってはいけません。
それにねじ込むことが出来るのは整数型のみです(多分)。
あとchar*じゃないとちゃんと動かないというのが良く分からないのですが。

コード:

void main(){
	char str[]="abcdefg";
	int* ip=(int*)str;

	cout<<(char)*ip<<endl;
	ip++;//4バイト進む
	cout<<(char)*ip<<endl;//eが出力される

	ip=(int*)&str[1];
	cout<<(char)*ip<<endl;
	ip++;//4バイト進む
	cout<<(char)*ip<<endl;//fが出力される
}
intだとこんな感じです。

コード:

void main(){
	char str[]="abcdefg";
	short int* ip=(short*)str;

	cout<<(char)*ip<<endl;
	ip++;//2バイト進む
	cout<<(char)*ip<<endl;//cが出力される

	ip=(short*)&str[1];
	cout<<(char)*ip<<endl;
	ip++;//2バイト進む
	cout<<(char)*ip<<endl;//dが出力される
}
ついでにshort int
なんとも恐ろしいコードですね。
こんなの見たら発狂します。

YuO
記事: 947
登録日時: 15年前
住所: 東京都世田谷区

Re: C++のファイルストリームに関して

#6

投稿記事 by YuO » 13年前

入れ違いで赤鬼さんの返信があったようですが,気にせずに投稿します。
赤鬼 さんが書きました:バイナリファイルで書き込む際にコンストラクタとデストラクタでしっかり開放してくれるらしいので便利だと思うのですが何故第一引数が(char*)なのですか?。
char *ではないです。
const char_type *です。

std::ofstreamではchar_typeは確かにcharですが,std::wofstreamではchar_typeはwchar_tになります。
赤鬼 さんが書きました:仕様だからとかそういうのはなしでお願いします。
何故char*なのかの理由が知りたいです。
予想するに,キャラクターベースのストリーム入出力を前提に書かれたクラスだからではないでしょうか。

basic_ostreamの出力はbasic_streambufを経由します。
そして,basic_streambufが定義される<streambuf>の項には,
The header <streambuf> defines types that control input from and output to character sequences.
と,キャラクターの並びであることが書かれています。

クラスデザイン上,文字列の入出力に最適なデザインにした,ということになるのだと思いますが,
つまりはそういう仕様,ということになります。

ISLe
記事: 2650
登録日時: 15年前
連絡を取る:

Re: C++のファイルストリームに関して

#7

投稿記事 by ISLe » 13年前

赤鬼 さんが書きました:ところでオブジェクトの内部表現とはなんでしょうか?
調べても出てこないし、良く分かりません。
いわゆる変数等の中身がメモリ上にどういうふうに並んでいるかということです。
要するにバイナリデータのことです。
C/C++言語の規格書に載ってます。
赤鬼 さんが書きました:あとchar*じゃないとちゃんと動かないというのが良く分からないのですが。
char*じゃないと内部表現に1バイトずつアクセスできないじゃないですか。
sizeofで指定したサイズ分ポインタ進めるというときもchar*である必要があります。

文字としてデザインされていても、言語規格上char*で内部表現にアクセスできるという仕様なのでバイナリデータも扱えるわけです。

Ryo

Re: C++のファイルストリームに関して

#8

投稿記事 by Ryo » 13年前

赤鬼 さんが書きました: ryoさん
回答有難うございます。

はい、そのとおりでした。
あれで扱えるのはucharの範囲みたいですね。
理由が良く分からないのでよろしかったら説明を頂けませんか?
ofs.write((char*)&a,1);

ofsは、char型を取り扱うように宣言されているbasic_ofstreamですから
第2引数に1を指定してあれば、sizeof(char)×1分のみを扱うということになります。
int a = 10は、内部的には 0a 00 00 00 ですから、
writeもreadでも、先頭の0aのみが読み書きされます。
これが、ucharの範囲分しか正常に扱えない理由です。
ofs.write((char*)&a,sizeof(int) * 1);
とすれば、int a を1個分扱うことはできます。

ちなみにfwriteの場合、const void*でデータバッファを渡すように宣言されていますが、
VS2008でソースをおっかけると 内部で_fwrite_nolock()を呼び出しており、
その中で、const char*にキャストしてから書き込んでいます。

もしかすると、
「void*は、型がないため素のポインタっぽいので、色んな型・サイズを扱う基礎的なものはvoid*のほうが似合う
 逆にchar*はcharが文字ゆえ、整数型の変数のポインタとして利用するのは違和感がある」
と、いうような感じ方をされているのでしょうか?

赤鬼

Re: C++のファイルストリームに関して

#9

投稿記事 by 赤鬼 » 13年前

YuOさん
なるほど、内部がキャラクタベースだったということですね。

ISLeさん
ISLe さんが書きました: いわゆる変数等の中身がメモリ上にどういうふうに並んでいるかということです。
要するにバイナリデータのことです。
C/C++言語の規格書に載ってます。
オブジェクトの内部表現っていうんですね。
規格書は読んだことが無かったので一度眺めてみます。
ISLe さんが書きました: char*じゃないと内部表現に1バイトずつアクセスできないじゃないですか。
1バイトずつアクセスするためにchar*なわけですか。
Ryo さんが書きました: ofs.write((char*)&a,1);
ofsは、char型を取り扱うように宣言されているbasic_ofstreamですから
第2引数に1を指定してあれば、sizeof(char)×1分のみを扱うということになります。
int a = 10は、内部的には 0a 00 00 00 ですから、
writeもreadでも、先頭の0aのみが読み書きされます。
これが、ucharの範囲分しか正常に扱えない理由です。
ofs.write((char*)&a,sizeof(int) * 1);
とすれば、int a を1個分扱うことはできます。
すみません質問の仕方を間違えたみたいです。
uchar(符号無し)の理由を知りたかったのです。
その説明だけではcharの範囲を扱ってるのではないのでしょうか?
例えば、int型の-1のビット表記は
11111111 11111111 11111111 11111111
ですよね。
そしてchar型分確保するので
11111111
これはchar型では-1
そして、readで取り出されます。
11111111
つまりchar型の-1バイト。

しかし実際には、書き込み取り出すと符号が無くなり
255になります。
この仕組みが分からないので質問させて頂きました。

・・・・と、此処まで書き込んでようやく気付きました。
11111111

int だから
00000000 00000000 00000000 11111111
こうなるからですね。
つまり、charならちゃんと-1になりますね。
(負数のキャスティングの際はコンパイラ?が自動的に変換しているのでしょうかね)
Ryo さんが書きました: ちなみにfwriteの場合、const void*でデータバッファを渡すように宣言されていますが、
VS2008でソースをおっかけると 内部で_fwrite_nolock()を呼び出しており、
その中で、const char*にキャストしてから書き込んでいます。
なるほど1バイト単位でアクセスできるcharを使い、渡されたサイズずつ書き込んでいるわけですね。
Ryo さんが書きました: もしかすると、
「void*は、型がないため素のポインタっぽいので、色んな型・サイズを扱う基礎的なものはvoid*のほうが似合う
 逆にchar*はcharが文字ゆえ、整数型の変数のポインタとして利用するのは違和感がある」
と、いうような感じ方をされているのでしょうか?
上は概ねそのとおりです。
void であればどんな型でもアドレスとして確保できるため(サイズはありませんが)、汎用的な処理には便利であると思っています。
下に関してはそうは思っていません、char型は文字では無く実際には1バイトの整数型ですから。
Cでは出力等の最、整数を文字に変換するだけですから、int型でも良いはずです。
(charの方が使いやすいし特化されてます、それにデバッガで追いかけたとき分わかり難いので当然使いませんが)
なので整数型の変数のポインタとして利用するのは違和感があるわけではありません。
理由が分からないと正しく使えないからです。
理由が分からないと理解できませんし、理解していないのに応用するのは普通は無理です。

かずまさん、Ryoさん、ISLeさん、YuOさん
丁寧に説明して頂き有難うございました。
概ね理解できたので解決とさせて頂きます。

閉鎖

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