C コードでの long int 値の書き込みと読み取り
-
12-09-2019 - |
質問
私は、いくつかの異なるオペレーティング システムやコンピューターで読み書きできるファイル形式に取り組んでいます。これらのコンピューターには、x86 マシンもあれば、x86-64 マシンもある必要があります。他にもいくつかのプロセッサが存在する可能性がありますが、それらは気にしません まだ.
このファイル形式には、次のように読み取られるいくつかの数値が含まれている必要があります。
struct LongAsChars{
char c1, c2, c3, c4;
};
long readLong(FILE* file){
int b1 = fgetc(file);
int b2 = fgetc(file);
int b3 = fgetc(file);
int b4 = fgetc(file);
if(b1<0||b2<0||b3<0||b4<0){
//throwError
}
LongAsChars lng;
lng.c1 = (char) b1;
lng.c2 = (char) b2;
lng.c3 = (char) b3;
lng.c4 = (char) b4;
long* value = (long*) &lng;
return *value;
}
そして次のように書かれています:
void writeLong(long x, FILE* f){
long* xptr = &x;
LongAsChars* lng = (LongAsChars*) xptr;
fputc(lng->c1, f);
fputc(lng->c2, f);
fputc(lng->c3, f);
fputc(lng->c4, f);
}
私のコンピュータでは動作しているように見えますが、他のコンピュータでは動作しない可能性や、ファイル形式がコンピュータ間で異なる可能性があるのではないか (たとえば、32 ビット コンピュータと 64 ビット コンピュータなど) と心配しています。私は何か間違ったことをしているでしょうか?数値ごとに一定のバイト数を使用するにはコードをどのように実装すればよいですか?
代わりに fread (これによりコードも高速化される可能性があります) を使用する必要がありますか?
解決
あなたが同じバイト数を取得し、アウトを確認するためにstdint.h
の型を使用します。
次に、あなたはあなたにはない、おそらくコードエンディアンの問題を扱うが残っています 本当に扱います。
*異なるエンディアンとプラットフォーム用に書かれたファイルで異なるバイトオーダーであなたを残しエイリアス文字と長いのシリアルます。
あなたはそのようなバイト何かを分解する必要があります:
char c1 = (val >> 0) & 0xff;
char c2 = (val >> 8) & 0xff;
char c3 = (val >> 16) & 0xff;
char c4 = (val >> 24) & 0xff;
そして、のようなものを使用して構図ます:
val = (c4 << 24) |
(c3 << 16) |
(c2 << 8) |
(c1 << 0);
他のヒント
むしろそれらの文字を持つ構造体を使用するよりも、より多くの数学的なアプローチを検討します:
long l = fgetc() << 24;
l |= fgetc() << 16;
l |= fgetc() << 8;
l |= fgetc() << 0;
これは、あなたが達成しようとしているのかについて、もう少し直接的かつ明確です。それはまた、より大きな数字を処理するループ内に実装することができる。
あなたはlong int型を使用する必要はありません。すなわち、異なるプラットフォーム上で異なるサイズとすることができるので、プラットフォームに依存しない形式の非スターターです。あなたはファイルに保存される必要がある値のどの範囲を決定する必要があります。 32ビットは、おそらく最も簡単です。
は、<全角>あなたは他のプラットフォームを心配しないと言う、まだの。私はあなたがあなたのファイル形式のバイト順を定義する必要があり、その場合には、それらを支援する可能性を、保持したい意味することを取りますよ。 x86のはリトルエンディアンであるので、あなたはそれが最高だと思うかもしれません。しかし、ビッグエンディアンは、それがネットワークで使われているので、何かが、ある場合には「標準」インターチェンジ順である。
あなたはビッグエンディアンのために行く場合は(「ネットワークバイト順序」):
// can't be bothered to support really crazy platforms: it is in
// any case difficult even to exchange files with 9-bit machines,
// so we'll cross that bridge if we come to it.
assert(CHAR_BIT == 8);
assert(sizeof(uint32_t) == 4);
{
// write value
uint32_t value = 23;
const uint32_t networkOrderValue = htonl(value);
fwrite(&networkOrderValue, sizeof(uint32_t), 1, file);
}
{
// read value
uint32_t networkOrderValue;
fread(&networkOrderValue, sizeof(uint32_t), 1, file);
uint32_t value = ntohl(networkOrderValue);
}
実際に、あなたもそれは同じ変数にそのネットワーク注文同等で「値」を交換するだけで少し混乱だ、2つの変数を宣言する必要はありません。
「ネットワークバイト順序は、」メモリ内の交換(ビッグエンディアン)の順序でビット結果のどんな配置になるように定義されているため、これは動作します。 C内の任意の格納されたオブジェクトが文字のシーケンスとして扱うことができるので、組合と混乱する必要はありませんが。そのため、エンディアンのための特殊なケースする必要がntohl / htonlがためのものです何んています。
これは遅すぎる場合は、、あなたはひどく最適化されたプラットフォーム固有のバイトスワップ、SIMDまたは何と考え始めることができます。またはあなたのプラットフォームのほとんどはリトルエンディアンになることを前提に、リトルエンディアンを使用しているので、それら全体で「平均的に」高速です。その場合は次のように記述またはx86上で、もちろんただ何もしない機能を、「ホストするためにリトルエンディアン」と「リトルエンディアンのホスト」を見つける必要があります。
私はstdint.hで定義されているほとんどのクロスアーキテクチャのアプローチは、uintXX_tタイプを使用することであると信じています。 ここでmanページを参照してください。int32_tはあなたのx86上の32ビット整数を与えるたとえばにおよびx86-64で。 彼らはすべての* NIX間でかなり標準的であるように私は、私のコードのすべてにデフォルトでこれらを使用し、何の問題もなかったしています。
仮定すると sizeof(uint32_t) == 4
, 、 がある 4!=24
可能なバイトオーダー。リトルエンディアンとビッグエンディアンが最も顕著な例ですが、他のバイトオーダーも同様に使用されています(例:PDP エンディアン)。
以下は、バイト シーケンスで表現される整数によって指定される任意のバイト順序に注意して、ストリームから 32 ビットの符号なし整数を読み書きするための関数です。 0,1,2,3
: エンディアン.h, エンディアン.c
ヘッダーはこれらのプロトタイプを定義します
_Bool read_uint32(uint32_t * value, FILE * file, uint32_t order);
_Bool write_uint32(uint32_t value, FILE * file, uint32_t order);
そしてこれらの定数
LITTLE_ENDIAN
BIG_ENDIAN
PDP_ENDIAN
HOST_ORDER