0

古いプログラムでは、unsigned char の配列を割り当ててデータ構造をバイトにシリアル化し、次のように int に変換しました。

*((*int)p) = value;

(pは 、 はunsigned char*保存valueする値です)。

これは問題なく動作しましたが、Sparc でコンパイルすると、不適切なアラインメントでメモリにアクセスしたために例外がトリガーされました。データ要素のサイズがさまざまであるため、pすぐに整列されなくなり、基になる Sparc 命令で整列が必要な int 値を格納するために使用されるとエラーが発生したため、これは完全に理にかなっています。

これはすぐに修正されました (値を char-array にバイト単位で書き出すことにより)。しかし、私はこの構造を何年にもわたって多くのプログラムで問題なく使用してきたので、これについて少し心配しています。しかし、明らかに私はいくつかの C ルール (厳密なエイリアシング?) に違反しています。このケースは簡単に発見されましたが、おそらく違反は、コンパイラーの最適化などにより、より微妙な他のタイプの未定義の動作を引き起こす可能性があります。このような構造は、長年にわたって多くの C コードで見てきました。ハードウェアによって交換されるデータ構造を構造体として記述し (もちろん pack(1) を使用)、それらを h/w レジスタなどに書き込むハードウェア ドライバーを考えています。したがって、一般的な手法のようです。

したがって、私の質問は、まさに上記によって違反されたルールと、ユースケースを実現するための適切な C の方法 (つまり、unsigned char の配列へのデータのシリアル化) です。もちろん、すべての関数に対してカスタムのシリアル化関数を記述して、バイト単位で書き出すことができますが、面倒であまり効率的ではないように思えます。

最後に、一般に、このエイリアシング規則に違反することによって、悪影響 (アライメントの問題以外) が予想される可能性はありますか?

4

2 に答える 2

1

はい、あなたのコードは厳密なエイリアシング規則に違反しています。C では、char*と そのsignedおよびunsigned対応するもののみが、他の型のエイリアスであると見なされます。

したがって、このような生のシリアル化を行う適切な方法は、 に配列を作成し、intsそれをunsigned charバッファとして扱うことです。

int arr[] = { 1, 2, 3, 4, 5 };
unsigned char* rawData = (unsigned char*)arr;

memcpyfwrite、または の他のシリアル化を行うことができ、rawDataそれは絶対に有効です。

逆シリアル化コードは次のようになります。

int* arr = (int*)calloc(5, sizeof(int));
memcpy(arr, rawData, 5 * sizeof(int));

確かに、信頼できるシリアル化を実装するにはendianness、およびその他の問題に注意する必要があります。padding

于 2015-09-28T21:45:48.280 に答える
0

構造体がメモリ内でどのように表現 (レイアウト) されるか、および構造体の開始アドレスが 1、2、4、8、... バイト境界に整列されているかどうかは、コンパイラとプラットフォームに固有です。したがって、構造体のメンバーのレイアウトについては、何も仮定しないでください。

メンバー型に特定のアラインメントが必要なプラットフォームでは、パディング バイトが構造体に追加されます (これは、上記で作成したステートメントと同じで、sizeof(struct Foo) >= データ メンバー サイズの合計です)。パディング...

ここで、fwrite()あるmemcpy()インスタンスから別のインスタンスへの構造体を、同じコンパイラと設定を持つ同じマシン上 (たとえば、同じプログラム内) で実行する場合、コンパイラによって追加されたデータ コンテンツとパディング バイトの両方を書き込みます。構造体全体を処理する限り、(少なくとも構造体内にポインター メンバーがない限り) ラウンド トリップを正常に行うことができます。

unsigned int はそのターゲット プラットフォームで適切な配置を必要とする可能性があるため、小さい型 ( unsigned charなど) を「大きい型」 ( unsigned intなど) にキャストし、それらの間で memcpyをキャストできるとは想定できません。通常、これを間違えると、バス エラーなどが表示されます。

malloc()最も一般的なケースでは、任意のタイプのデータのヒープメモリを取得する一般的な方法です。アラインメント要件とは関係なく、バイト配列であろうと構造体であろうと。できないシステムは存在しませんstruct Foo *ps = malloc(sizeof(struct Foo))。アラインメントが重要なプラットフォームでは、malloc はアラインされていないアドレスを返しません。これは、構造体にメモリを割り当てようとして、コードが壊れる可能性があるためです。サイキックではないのでmalloc()、バイト配列の割り当てに使用すると、「構造体互換の整列された」ポインターも返されます。

構造体全体を記述するような「アドホック」シリアライゼーションの形式は、シリアライズされたデータを他のマシンや他のアプリケーション (または誰かがコンパイラ設定をいじった可能性のある同じアプリケーションの将来のバージョン) と交換する必要がない限り、有望なアプローチにすぎません。 、アライメントに関連する)。

移植性があり、より信頼性が高く堅牢なソリューションを探している場合は、メイン ストリームのシリアル化パッケージの 1 つを使用することを検討する必要があります。その 1 つが前述の Google プロトコル バッファです。

于 2015-09-28T21:15:14.693 に答える