5

問題のデバッグ中に、次の問題が発生しました。(マイナーなコードエラーは無視してください。コードは説明のためだけのものです。)

次の構造体が定義されています。

typedef struct box_t {
  uint32_t x;
  uint16_t y;
} box_t;

この構造体のインスタンスは、関数から関数に値で渡されます(明らかに単純化されています)。

void fun_a(box_t b)
{
    ... use b ...
}

void fun_b(box_t bb)
{
    // pass bb by value
    int err = funa(bb);
}

void fun_c(void)
{
    box_t real_b;
    box_t some_b[10];
    ...
    ... use real_b and some_b[]  ...
    ...
    funb(real_b);
    funb(some_b[3]);
    ...
    box_t copy_b = some_b[5];
    ...
}

場合によっては、box_tの2つのインスタンスが次のように比較されます。

 memcmp(bm, bn, sizeof(box_t));

いくつかのネストされた呼び出し内で、box_targのバイトは次のようなものを使用してダンプされました。

char *p = (char*) &a_box_t_arg;
for (i=0; i < sizeof(box_t); i++) {
    printf(" %02X", *p & 0xFF);
    p++;
}
printf("\n");

sizeof(box_t)は8です。2つのパッドバイトがあります(uint16_tの後にあるものとして検出されます)。ダンプは、構造体のフィールドが等しいことを示しましたが、パッドバイトは等しくありませんでした。これにより、memcmpが失敗しました(当然のことながら)。

興味深い部分は、「破損した」パッド値がどこから来たのかを発見することでした。逆方向に追跡した後、box_tインスタンスの一部がローカル変数として宣言され、次のように初期化されていることがわかりました。

box_t b;
b.x = 1;
b.y = 2;

上記は、「ガベージ」(bに割り当てられたスタックスペースにあったもの)を含むように見えるパッドバイトを初期化しません(表示されます)。ほとんどの場合、初期化はを使用して行われましたmemset(b, 0, sizeof(box_t))

問題は、(1)構造体の代入または(2)値の受け渡しのいずれかによってbox_tのインスタンスを初期化すると、常にsizeof(box_t)のmemcpyと同等になるかどうかです。'実際のフィールド'の6バイトだけがコピーされる(そしてパッドバイトはコピーされない)というのはこれまでにありますか?

デバッグから、memcpy sizeof(box_t)と同等のものが常に実行されているように見えます。これを実際に指定するものはありますか(たとえば、標準で)?デバッグが進むにつれて、パッドバイトの処理に関して何が期待できるかを知ることは役に立ちます。

ありがとう!(Ubuntu LTS 10.464ビットでGCC4.4.3を使用)

ボーナスポイントの場合:

void f(void)
{
    box_t ba;
    box_t bb;
    box_t bc;

sizeof()が8を示している間、3つのインスタンスは16バイト離れて割り当てられます。なぜ余分なスペースがあるのですか?

4

3 に答える 3

5

パディング バイトの値は指定されていません (C99/C11 6.2.6.1 §6):

値が構造体型または共用体型のオブジェクト (メンバー オブジェクトを含む) に格納される場合、パディング バイトに対応するオブジェクト表現のバイトは指定されていない値を取ります。

脚注 42/51 (C99:TC3、C1x ドラフト) も参照してください。

したがって、たとえば、構造体の割り当てでは、パディング ビットをコピーする必要はありません。

コンパイラは、必要に応じてパディングをコピーするかしないかを自由に選択できます。x86[1] では、末尾の 2 バイトのパディング バイトはコピーされるが、4 バイトはコピーされない (たとえば、構造体のアトミックな読み取りを許可するために、8 バイトのアライメントが必要な場合があるため、32 ビット ハードウェアでも発生する可能性があると思います)。double値)。

[1]実際の測定は行われませんでした。


答えを拡張するには:

標準では、パディング バイトに関する保証はありませんただし、静的ストレージ期間でオブジェクトを初期化すると、パディングがゼロになる可能性が高くなります。しかし、そのオブジェクトを使用して割り当てを介して別のオブジェクトを初期化すると、すべての賭けが再びオフになります (また、末尾のパディング バイト (測定は行われません) は、コピーから除外される特に良い候補になると思います)。

memset()andを使用するとmemcpy()、個々のメンバーに割り当てる場合でも、パディングが無効になる可能性があるため、合理的な実装でパディング バイトの値を保証する方法です。ただし、原則として、コンパイラーはいつでも「背後で」パディング値を自由に変更できます (これは、メンバーをレジスターにキャッシュすることに関連している可能性があります - 再び推測します)、おそらくvolatileストレージを使用することで回避できます。

私が考えることができる唯一の合理的に移植可能な回避策は、追加のパディングが導入されていないことをコンパイラ固有の手段で検証しながら、適切なサイズのダミーメンバーを導入することによってメモリレイアウトを明示的に指定することです( gccの場合) __attribute__ ((packed))-Wpadded

于 2012-06-08T18:58:14.393 に答える
3

C11 では、匿名の構造体と共用体のメンバーを定義できます。

typedef union box_t {
  unsigned char allBytes[theSizeOfIt];
  struct {
    uint32_t x;
    uint16_t y;
  };
} box_t;

そのユニオンは以前とほぼ同じように動作し、.xetc にアクセスできますが、デフォルトの初期化と割り当てが変更されます。変数が次のように正しく初期化されていることを常に確認する場合:

box_t real_b = { 0 };

またはこのように

box_t real_a = { .allBytes = {0}, .x = 1, .y = 2 };

すべてのパディング バイトは に正しく初期化する必要があります0整数型にパディング ビットがある場合、これは役に立ちませんが、少なくともuintXX_t選択した型には定義上それらがありません。

gcc とフォロワーは、まだ完全に C11 になっていない場合でも、これを拡張機能として既に実装しています。

編集: P99には、一貫した方法でそれを行うマクロがあります。

#define P99_DEFINE_UNION(NAME, ...)                     \
 union NAME {                                           \
   uint8_t p00_allbytes[sizeof(union { __VA_ARGS__ })]; \
   __VA_ARGS__                                          \
 }

つまり、配列のサイズは、そのサイズだけのために「タグなし」共用体を宣言することによって決定されます。

于 2012-06-08T19:31:38.753 に答える
2

クリストフが言ったように、パディングに関する保証はありません。最善の策は、memcmp2 つの構造体を比較するために使用しないことです。間違った抽象化レベルで動作します。memcmpメンバーの値を比較する必要がある間、表現でバイト単位で機能します。

2 つの構造体を取り、各メンバーを個別に比較する別の比較関数を使用することをお勧めします。このようなもの:

int box_isequal (box_t bm, box_t bn)
{
    return (bm.x == bn.x) && (bm.y == bn.y);
}

おまけに、3 つのオブジェクトは別々のオブジェクトであり、同じ配列の一部ではなく、それらの間のポインター演算は許可されていません。関数ローカル変数として、それらは通常スタックに割り当てられます。これらは分離されているため、コンパイラはパフォーマンスなどの最適な方法で整列できます。

于 2012-06-08T21:25:11.717 に答える