C++ データ メンバーのアライメントと配列のパッキング
-
16-09-2019 - |
質問
コードレビュー中に、次のような単純な構造を定義するコードを見つけました。
class foo {
unsigned char a;
unsigned char b;
unsigned char c;
}
他の場所では、これらのオブジェクトの配列が定義されています。
foo listOfFoos[SOME_NUM];
その後、構造体がバッファーに生コピーされます。
memcpy(pBuff,listOfFoos,3*SOME_NUM);
このコードは次の前提に基づいています。a.) foo のサイズは 3 で、パディングは適用されません。 b.) これらのオブジェクトの配列は、間にパディングなしでパックされます。
2 つのプラットフォーム (RedHat 64b、Solaris 9) 上の GNU で試してみましたが、両方で動作しました。
上記の仮定は有効ですか?そうでない場合、どのような条件下で (例:OS/コンパイラの変更など)失敗する可能性がありますか?
解決
オブジェクトの配列は、連続であることが要求されるので、パディングが(ほぼ同じ効果をもたらす)オブジェクトの末尾に追加することができるが、オブジェクトの間にパディングはありませんです。
あなたはシャアで作業していることを考えると、仮定はおそらく正しい少なからずありますが、C ++標準では、確かにそれを保証するものではありません。異なるコンパイラ、またはあなたの現在のコンパイラに渡されるフラグでだけでも変更はパディングが構造体の要素や構造体の最後の要素以下、またはその両方の間に挿入されているにつながる可能性があります。
他のヒント
間違いなく行うために、より安全になります:
sizeof(foo) * SOME_NUM
あなたはこのようなあなたの配列をコピーする場合は、あなたが使用する必要があります。
memcpy(pBuff,listOfFoos,sizeof(listOfFoos));
このは常に限り、あなたは同じサイズにpBuffを割り当てられるように動作します。 この方法で、あなたはすべてのパディングとアライメントに何の仮定をしていません。
ほとんどのコンパイラが含まれる最大タイプの必要配向への構造体またはクラスを整列させます。何の整列とパディングを意味しませんが、あなたはたとえば、短いを追加する場合は、あなたのクラスは最後の文字と、あなたの短い間に追加1バイトのパディングを持つ大規模な6バイトになる文字のあなたのケースでます。
これが機能する理由は、構造内のすべてのフィールドが 1 つを整列する char であるためだと思います。1 に整列しないフィールドが少なくとも 1 つある場合、構造体/クラスの整列は 1 になりません (整列はフィールドの順序と整列によって異なります)。
いくつかの例を見てみましょう:
#include <stdio.h>
#include <stddef.h>
typedef struct {
unsigned char a;
unsigned char b;
unsigned char c;
} Foo;
typedef struct {
符号なしの短い i;
unsigned char a;
unsigned char b;
unsigned char c;
} Bar;
typedef struct { Foo F[5]; } F_B;
typedef struct { Bar B[5]; } B_F;
#define ALIGNMENT_OF(t) offsetof( struct { char x; t test; }, test )
int main(void) {
printf("Foo:: Size: %d; Alignment: %d\n", sizeof(Foo), ALIGNMENT_OF(Foo));
printf("Bar:: Size: %d; Alignment: %d\n", sizeof(Bar), ALIGNMENT_OF(Bar));
printf("F_B:: Size: %d; Alignment: %d\n", sizeof(F_B), ALIGNMENT_OF(F_B));
printf("B_F:: Size: %d; Alignment: %d\n", sizeof(B_F), ALIGNMENT_OF(B_F));
}
実行すると、結果は次のようになります。
Foo:: Size: 3; Alignment: 1
Bar:: Size: 6; Alignment: 2
F_B:: Size: 15; Alignment: 1
B_F:: Size: 30; Alignment: 2
Bar と F_B にはアライメント 2 があり、フィールド i が適切に配置されていることがわかります。また、バーのサイズが 5ではなく6. 。同様に、B_F (Bar の 5) のサイズは次のようになります。 25ではなく30.
したがって、代わりにハードコードの場合は、 sizeof(...)
, ここで問題が発生します。
お役に立てれば。
これは、すべてのメモリ配置にダウンしています。典型的な32ビットマシンは、試行ごとのメモリの4つのバイトを読み取りまたは書き込み。それはノー混乱パディングの問題で簡単にその4バイトに該当するので、この構造は、問題から安全です。
今の構造は、次のようなだった場合
class foo {
unsigned char a;
unsigned char b;
unsigned char c;
unsigned int i;
unsigned int j;
}
あなたの同僚・ロジックは、おそらくにつながる。
memcpy(pBuff,listOfFoos,11*SOME_NUM);
(3シャア= 3バイト、2つのint値= 2 * 4バイト、SO 3 + 8)
残念ながら、構造をパディングに起因するが、実際に12バイトを占めます。あなたはその4バイトワードに3文字のint型に合うことができない、ので、それ自身の言葉にINTをプッシュがパディングされた空間の1つのバイトがありますので、これがあります。これは、データ型になるより多様な問題のより多くなります。
このようなものが使用されている状況で、私はそれを避けることができない、私は仮定は、もはや保持しないコンパイル休憩を作ってみます。私は、次の(または Boost.StaticAssert 状況が許す場合):
static_assert(sizeof(foo) <= 3);
// Macro for "static-assert" (only usefull on compile-time constant expressions)
#define static_assert(exp) static_assert_II(exp, __LINE__)
// Macro used by static_assert macro (don't use directly)
#define static_assert_II(exp, line) static_assert_III(exp, line)
// Macro used by static_assert macro (don't use directly)
#define static_assert_III(exp, line) enum static_assertion##line{static_assert_line_##line = 1/(exp)}
私は安全されていると私は数えるsizeof(foo)
とマジックナンバー3を交換したでしょう。
私の推測では、将来のプロセッサアーキテクチャ用に最適化されたコードは、おそらくパディングのいくつかのフォームをご紹介しますということです。
とバグのその種を追跡しようとしている本当の痛みです!
はsizeof(FOO)を使用して安全な賭けです。いくつかのコンパイラ(埋め込まれた世界では、特に難解なもの)のクラスに4バイトのヘッダを追加します。他の人はあなたのコンパイラ設定に応じて、ファンキーなメモリ・アライメントトリックを行うことができます。
は、主流のプラットフォームのために、あなたはおそらく大丈夫だが、そのことを保証ます。
まだ)はsizeof(に問題がある可能性があります。それらの一方のコードは、その場合はsizeof()異なる結果を与える、ことなく、パディングや他でコンパイル可能性があります。配列データは、他のコンピュータに渡された場合、予期された場所で配列の要素が見つからないので、それが誤解されます。 一つの解決策は、可能な限り(1)の#pragmaパックが使用されていることを確認することですが、それは配列のために十分ではないかもしれません。最高の問題を予測し、配列要素ごとに8バイトの倍数にパディングを使用することである。