17

コードレビュー中に、次のように単純な構造を定義するコードに出くわしました。

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/コンパイラの変更など) で失敗する可能性がありますか?

4

9 に答える 9

22

次のようにする方が間違いなく安全です。

sizeof(foo) * SOME_NUM
于 2009-11-04T20:29:18.943 に答える
20

オブジェクトの配列は連続している必要があるため、オブジェクト間にパディングはありませんが、オブジェクトの末尾にパディングを追加することはできます (ほぼ同じ効果が得られます)。

char を使用していることを考えると、仮定は正しい場合が多いのですが、C++ 標準は確かにそれを保証していません。別のコンパイラ、または現在のコンパイラに渡されるフラグの変更だけでも、構造体の要素の間または構造体の最後の要素の後に、またはその両方にパディングが挿入される可能性があります。

于 2009-11-04T20:30:54.487 に答える
6

このように配列をコピーする場合は、使用する必要があります

memcpy(pBuff,listOfFoos,sizeof(listOfFoos));

これは、pBuff を同じサイズに割り当てている限り、常に機能します。このようにして、パディングとアラインメントについてまったく仮定していません。

ほとんどのコンパイラは、含まれる最大の型の必要なアラインメントに構造体またはクラスをアラインします。文字の場合、これは配置とパディングがないことを意味しますが、たとえばショートを追加すると、クラスは 6 バイトになり、最後の文字とショートの間に 1 バイトのパディングが追加されます。

于 2009-11-04T20:40:22.790 に答える
5

これが機能する理由は、構造体のすべてのフィールドが 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 {
    unsigned short 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(...)ここで問題が発生します。

お役に立てれば。

于 2009-11-04T21:55:58.980 に答える
2

私は安全だったので、マジックナンバー3をsizeof(foo)私が推測するものに置き換えました.

私の推測では、将来のプロセッサ アーキテクチャ向けに最適化されたコードでは、何らかの形式のパディングが導入される可能性があります。

そして、その種のバグを追跡しようとするのは本当に苦痛です!

于 2009-11-04T20:30:48.457 に答える
2

それはすべて、メモリの配置に帰着します。一般的な 32 ビット マシンは、1 回の試行で 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 バイト、つまり 3 + 8)

残念ながら、パディングにより、構造体は実際には 12 バイトを占めます。これは、3 つの char と 1 つの int をその 4 バイトの単語に収めることができず、1 バイトの埋め込みスペースがあり、int をそれ自体の単語にプッシュするためです。これは、データ型が多様化すればするほど問題になります。

于 2009-11-04T20:41:57.390 に答える
2

このようなものが使用され、それを避けることができない状況では、前提が成り立たなくなったときにコンパイルを中断させようとします。私は次のようなものを使用します (状況が許せば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)}
于 2010-03-09T12:54:40.617 に答える
1

他の人が言ったように、 sizeof(foo) を使用する方が安全です。一部のコンパイラ (特に組み込みの難解なコンパイラ) は、クラスに 4 バイトのヘッダーを追加します。他の人は、コンパイラの設定に応じて、ファンキーなメモリ アライメント トリックを行うことができます。

主流のプラットフォームの場合は、おそらく問題ありませんが、保証するものではありません.

于 2009-11-04T20:36:44.280 に答える