2

私は、いくつかの異なるオペレーティング システムとコンピューターで読み書きできるファイル形式に取り組んでいます。これらのコンピューターの一部は 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 (コードも高速になる可能性があります) を使用する必要がありますか?

4

6 に答える 6

8

stdint.hin と out のバイト数が同じになるようにするには、in の型を使用します。

次に、エンディアンの問題に対処する必要がありますが、これはおそらくコードでは実際には処理されません。

エイリアス化された char* を使用して long をシリアライズすると、エンディアンが異なるプラットフォーム用に書き込まれたファイルに異なるバイト順が残ります。

次のようにバイトを分解する必要があります。

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);
于 2009-07-09T19:24:04.180 に答える
1

また、エンディアンの問題が発生する場合もあります。発生する可能性のある移植性の問題を処理するNetCDFHDFなどを使用しないのはなぜですか?

于 2009-07-09T19:24:32.573 に答える
1

文字を含む構造を使用するのではなく、より数学的なアプローチを検討してください。

long l  = fgetc() << 24;
     l |= fgetc() << 16;
     l |= fgetc() <<  8;
     l |= fgetc() <<  0;

これは、達成しようとしていることについて、もう少し直接的で明確です。より大きな数を処理するためにループで実装することもできます。

于 2009-07-09T19:30:28.240 に答える
1

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 に格納されたオブジェクトはすべて char のシーケンスとして扱うことができるため、共用体をいじる必要はありません。それが ntohl/htonl の目的であるため、エンディアンを特別にケース化する必要はありません。

これが遅すぎる場合は、SIMD などを使用して、非常に最適化されたプラットフォーム固有のバイトスワッピングについて考え始めることができます。または、ほとんどのプラットフォームがリトルエンディアンであり、プラットフォーム全体で「平均して」高速であるという前提で、リトルエンディアンを使用します。その場合、「ホストからリトルエンディアンへ」および「リトルエンディアンからホストへ」関数を作成または検索する必要がありますが、もちろん x86 では何もしません。

于 2009-07-09T21:15:47.247 に答える
0

最もクロスアーキテクチャのアプローチは、stdint.hで定義されているuintXX_tタイプを使用することだと思います。こちらのmanページを参照してください。たとえば、int32_tは、x86およびx86-64で32ビット整数を提供します。これらはすべてのコードでデフォルトで使用されており、すべての* NIXでかなり標準的であるため、問題は発生していません。

于 2009-07-10T02:28:43.660 に答える
0

リトルエンディアンとビッグエンディアンが最も顕著な例ですが、他のものも使用されています (例: PDP エンディアン) sizeof(uint32_t) == 44!=24

以下は、ストリームから 32 ビットの符号なし整数を読み書きするための関数であり、表現がバイト シーケンスである整数によって指定される任意のバイト オーダーに注意します0,1,2,3: endian.hendian.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
于 2009-07-10T15:23:38.590 に答える