質問させてください。
fseekでファイルの末尾からストリームをある構造体分移動するのに
第二引数にマイナス値を指定したいのですが、どうすればよいのかわかりません。
fseek(fp, sizeof(HogeStruct), SEEK_END);
例:
sizeof(HogeStruct) == 22byte
を -22 にしたいのです。
よろしくお願いします。
- sizeof(HogeStruct)
とか
-1 * sizeof(HogeStruct)
とか。
HogeStructのアライメントが心配かも。
(22Byteってことですし)
あ、sizeofで22Byteだからいいのか。orz
aetos様 Blue様、ありがとうございます。
マイナスにする処理は下記方法でできました。
-1 * sizeof(HogeStruct)
>HogeStructのアライメントが心配かも。
実は、アライメントの問題も抱えています。
sizeof(HogeStruct) == 28
ですが、実際にファイルに格納されているサイズは22なのです。
現在、ZIPファイルを作成しておりまして、
最後のヘッダ部分の取得を試みております。
お恥ずかしながら、アライメントという現象は最近知ったばかりです。
fseekで一度に構造体に読むのはやめたほうかよいのでしょうか?
> fseekで一度に構造体に読むのはやめたほうかよいのでしょうか?
何をどこまで求めるか、次第
そのソースコードに汎用性移植性を求めるなら「やめとけ」となるし
移植性無視して、短さや速度を求めるなら「やればいい」となる
ZIP の EndCentDirHeader なら確かに 22 バイトだけど、俺なら
struct 直読み (fseek+fread) でこれを読み取りたいとは微塵も思わない・・・
BigEndian 系マシンに対して移植性0
ありがとうございます。
>そのソースコードに汎用性移植性を求めるなら「やめとけ」となるし
>移植性無視して、短さや速度を求めるなら「やればいい」となる
まさに今、移植性・汎用性を求めてやっています。
構造体サイズを一度に読む方法を変えるとすると、
ZIPファイル末尾から、構造体メンバ分を1つ1つ読んでいく方法になるのでしょうか?
fseek()を使わない方向で話が進んでいるところで蒸し返すようですが、
ちょっと気になったので横から失礼。
> - sizeof(HogeStruct)
> とか
> -1 * sizeof(HogeStruct)
どこまで移植性を気にするかにもよりますが、これ、ちょっとマズいことになる
可能性を考えた方がいいかも。
size_tがint以上の大きさを持つ型 (*1) だと、単項のマイナスを付けたり、-1を
掛けたりしても符号なしのままなので、非常に大きな数 (*2) になってしまいます。
fseek()に食わせるならまだしも (*3) 、VCの_fseek64() (*4)や、ostream::operator<<()
などに食わせると予期しない結果になります。
-(ptrdiff_t)sizeof(HogeStruct) …… size_tに対応する符号付き型でキャスト
とか
-(long)sizeof(HogeStruct) …… fseek()の引数の型でキャスト
といった感じにした方がよいのでは?
(*1) VCを含め、大抵の環境がそうなってますね。
(*2) 具体的には size_tの最大値 + 1 - sizeof(HogeStruct)
(*3) 細かいことを言うと、これも若干問題がありますが、重箱の隅過ぎるので割愛。
(*4) 64ビット版ならsize_tも64ビットだから大丈夫?
移植性・汎用性ってのをどの範囲で求めるか、まあその辺から話は始まるわけだけど
・パイプやテープデバイスを使うかどうか?
(パイプやテープデバイスは seek 不能、ただ頭から読むだけが可能)
・超巨大ファイル (2GB 超) を取り扱うかどうか?
(たいていの処理系では 2GB 超ファイルに対しては fseek64 等異なる関数が必要)
こっちは直接 C/C++ の問題ではないので今回扱わないことにして
・C89/C++ において、基本型のサイズは決まっていない
・エンディアンの考慮が必要
ってあたりを考える必要がある。
ファイル上のデータフォーマットと
任意のコンパイラで作った struct の内部構造は
必ずしも一致しない、というのは既に挙げられたとおり。
fseek して EndCentDir を読むだけであれば
・ファイルフォーマット的に 22 バイトなわけであるから、シークの適切なコードは
fseek(f, -22L, SEEK_END); であって -sizeof(...) ではない
・struct EndCentDirHeader のマシン内部フォーマットは、
ファイル上のデータ並びとは異なる可能性がある
・なので1項目づつ変換するルーチンが必要
となるわけだ。
char file_endcentdir[22];
fseek(f, -22L, SEEK_END);
fread(file_endcentdir, 1, 22, f);
struct endcentdir_t endcentdir;
endcentdir.signature=F2M_32(file_endcentdir+0);
endcentdir.disknum=F2M_16(file_endcentdir+4);
endcentdir.startdisknum=F2M_16(file_endcentdir+6);
俺なら、こんな感じで実装するかな。
F2M_32 は 32bit の ファイル→マシン内部フォーマット変換関数またはマクロ
F2M_16 は 16bit の ファイル→マシン内部フォーマット変換関数またはマクロ
( ntohl, ntohs 相当と考えてくれ )
皆様、ありがとうございます。
tetrapod様が提示して頂いた方法で実装することにします。
ただ、
>endcentdir.signature=F2M_32(file_endcentdir+0);
>endcentdir.disknum=F2M_16(file_endcentdir+4);
>endcentdir.startdisknum=F2M_16(file_endcentdir+6);
>俺なら、こんな感じで実装するかな。
>F2M_32 は 32bit の ファイル→マシン内部フォーマット変換関数またはマクロ
>F2M_16 は 16bit の ファイル→マシン内部フォーマット変換関数またはマクロ
上記ご説明のF2M_32(...) の意味が未熟なため理解できません。
できたら解説をお願い致します。
エンディアンが何かわかっていれば hton/ntoh が何か調べれば実装できる
検索すべきキーワードは既に出しているのでまずは調査して味噌
endcentdir.signature = ntohl((u_long)file_endcentdir+0);
endcentdir.disknum = ntohs((u_short)file_endcentdir+4);
endcentdir.startdisknum = ntohs((u_short)file_endcentdir+6);
としましたが、意図する値が取得できません。
ntohlとntohsがリトルエンディアンをビックエンディアンに変換する関数というのはわ
かったんですが、なぜうまくいかないのかわかりません。
ご教授ください!
まずそもそもの話としてエンディアンって何か本当にわかっているわけ?
先ほどのコメントの状況からして理解できているとは思えないのだが。
ZIP ファイルのファイル上データフォーマットは LE (Little Endian) 固定。
一方で、移植性を向上させるには
・マシン内部形式が LE であるか BE であるかは知らなくていい
・マシン内部ビットサイズは知らなくていい
ように作る必要がある。
俺の先のコメントは、上記理想を実現するために
「LE固定形式をマシン内部形式にそぐう形で取り出す」マクロ/関数を作れ
ということだったわけだ。誰も hton/ntoh 使えとは書いていないんだが・・・
もうめんどくさいので例示しちゃえ・・・エラーチェック類一切省略。
unsigned short f2m16(const unsigned char* p) {
return p[0] | (p[1]<<8);
}
unsigned long f2m32(const unsigned char* p) {
return p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24);
}
void read_ecd(FILE* f, struct end_central_dir_t* mecd) {
unsigned char fecd[22];
fseek(f, -22L, SEEK_END);
fread(fecd, 1, 22, f);
mecd->signature=f2m32(fecd+0);
mecd->disknum=f2m16(fecd+4);
以下略
ああ、なるほど。
そういう意味のコメントだったんですね。
ありがとうございますた。