いくつかの問題があります:
効率上の理由から、コンパイラはプロセッサのレジスタ サイズに等しい境界で変数を整列させます。つまり、32 ビット システムでは、これは 32 ビット (4 バイト) 境界になります。さらに、構造体には「ギャップ」があり、構造体のメンバーを 32 ビット境界に揃えることができます。言い換えれば、構造体は「詰め込まれた」わけではありません。これを試して:
#include <stdio.h>
typedef struct
{
char one[5]; /* 5 bytes */
unsigned int two; /* 4 bytes */
}
structure;
structure my_structure;
char array[] =
{
0x41, 0x42, 0x43, 0x44, 0x00, /* ABCD\0 */
0x00, 0xbc, 0x61, 0x4e /* 12345678 (base 10) */
};
int main(int argc, char **argv)
{
const int sizeStruct = sizeof(structure);
printf("sizeof(structure) = %d bytes\n", sizeStruct);
const int sizeArray = sizeof(array);
printf("sizeof(array) = %d bytes\n", sizeArray);
return 0;
}
さまざまなサイズが表示されるはずです。
#pragma または属性ディレクティブを使用して、この動作をオーバーライドできます。gcc では、属性を使用して構造定義を変更できます。たとえば、上記のコードを変更して「packed」属性を追加します (gcc が必要):
typedef struct __attribute__((packed))
その後、プログラムを再度実行します。サイズは今と同じはずです。
注: ARMv4 などの一部のプロセッサ アーキテクチャでは、32 ビット変数を 32 ビット境界に揃える必要があります。そうしないと、プログラムが実行されません (例外が発生します)。"aligned" および "packed" プラグマまたは属性のコンパイラ ドキュメントを参照してください。
次の問題はバイトオーダーです。これを試して:
printf("0x%08X\n", 12345678);
12345678 は 16 進数で 0x00BC614E です。あなたの例とあなたが得ている出力から、プラットフォームが「リトルエンディアン」であることがわかります。「リトル エンディアン」システムでは、数値0x00BC614E
は最下位バイトから始まるバイト シーケンスとして格納されます0x4E, 0x61, 0xBC, 0x00
。したがって、配列定義を変更します。
char array[] =
{
0x41, 0x42, 0x43, 0x44, 0x00, /* ABCD\0 */
0x4E, 0x61, 0xBC, 0x00, /* 12345678 (base 10) */
};
これで、プログラムは 12345678 を出力します。
また、unsigned int を出力するには %u を使用する必要があることに注意してください。
char 文字列のコピーは、特に異なるエンコーディング (Unicode など) を許可する必要がある場合は、ワームの可能性があります。少なくとも、コピー先のバッファーがオーバーランから保護されていることを確認する必要があります。
改訂されたコード:
#include <stdio.h>
#include <string.h>
typedef struct
{
char one[5]; /* 5 bytes */
unsigned int two; /* 4 bytes */
}
structure;
structure my_structure;
char array[] =
{
0x41, 0x42, 0x43, 0x44, 0x00, /* ABCD\0 */
0x4E, 0x61, 0xBC, 0x00, /* 12345678 (base 10) */
};
int main()
{
// copy string as a byte array
memcpy(&my_structure.one, &array[0], sizeof(my_structure.one));
// copy uint
my_structure.two = *((unsigned int *)(&array[5]));
printf("%s\n", my_structure.one);
printf("%u\n", my_structure.two);
return 0;
}
最後に、別のプラットフォームへのコードの移植が困難になるため、通常、パックされたデータ構造に依存することはお勧めできません。ただし、プロトコル パケットをパック/アンパックする必要がある場合もあります。これらの特殊なケースでは、通常、データ型ごとに関数のペアを使用して各アイテムを手動でパック/アンパックするのが最善であり、移植性が最も高くなります。
エンディアンの問題は別のトピックに譲ります。:-)