2

9 バイトの配列があり、これらのバイトを構造体にコピーしたい:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

typedef struct _structure {
    char one[5];        /* 5 bytes */
    unsigned int two;   /* 4 bytes */
} structure;

int main(int argc, char **argv) {

    structure my_structure;

    char array[]    = {
        0x41, 0x42, 0x43, 0x44, 0x00,   /* ABCD\0 */
        0x00, 0xbc, 0x61, 0x4e          /* 12345678 (base 10) */
    };

    memcpy(&my_structure, array, sizeof(my_structure));

    printf("%s\n", my_structure.one);   /* OK, "ABCD" */
    printf("%d\n", my_structure.two);   /* it prints 1128415566 */

    return(0);
}

my_structure構造体の最初の要素 はone正しくコピーされています。ただし、my_structure.two1128415566 が含まれていますが、12345678 を期待arrayしています。my_structureサイズが異なり、サイズが同じであっても、two. この問題を解決するにはどうすればよいですか?

4

3 に答える 3

7

いくつかの問題があります:

効率上の理由から、コンパイラはプロセッサのレジスタ サイズに等しい境界で変数を整列させます。つまり、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;
}

最後に、別のプラットフォームへのコードの移植が困難になるため、通常、パックされたデータ構造に依存することはお勧めできません。ただし、プロトコル パケットをパック/アンパックする必要がある場合もあります。これらの特殊なケースでは、通常、データ型ごとに関数のペアを使用して各アイテムを手動でパック/アンパックするのが最善であり、移植性が最も高くなります。

エンディアンの問題は別のトピックに譲ります。:-)

于 2012-07-24T01:59:23.593 に答える
1

Mysticialがすでに説明したように、構造の整列の効果が表示されます。コンパイラは、要素をワードサイズの境界に整列します。つまり、4バイト境界の32ビットコードで、文字間に3バイトのギャップを効果的に残します。 [5]と次の要素。

gccまたはVisualStudioを使用する場合#pragma pack(1)、コンパイラがデフォルトで使用する「優先」パッキングをオーバーライドできます。この例では、1バイト境界で、つまり「穴」なしで命令するようにコンパイラに指示します。これは、組み込みシステムでバイトのブロックを構造にマップするのに役立つことがよくあります。他のコンパイラについては、コンパイラのマニュアルを参照してください。

于 2012-07-23T23:21:31.333 に答える
0

他の回答がすでに示しているように、配置の問題が発生しています。コンパイラは、使用しているプロセッサの種類に応じて、長いまたはクワッドワードの境界に沿ってデータ構造を整列させる傾向があります。つまり、構造体で宣言したものがアラインされていない場合、コンパイラはアラインメント バイトをパックし、それらを表示することは想定されていません。

ところで、むかしむかし、全世界がインテルではありませんでした。それぞれ独自のアライメント要件を持つ他のプロセッサがあったため、アライメントは、特に異なるプロセッサ ファミリ間でのブート ROM コードの移植など、私たち全員がかなり取り組んだものでした。

このような問題が発生した場合は、次のようにコードを変更して少し実験を行うことをお勧めします。

1) コードに宣言structure * pStructure;を追加します。

2) pStructure = (構造 *) 配列を追加します。` 配列の宣言の直後。

3) 次に、memcpy がある行にブレークポイントを設定します。

ブレークポイントに到達したら、印刷または表示コマンドを入力します (gdb は p を使用します)。

p pStructure->one
(gdb) p pStructure->one
$4 = "ABCD"

そして、次の

(gdb) p pStructure->two
$7 = 3486515278

4 バイトの数値については、予期した数値が表示されていないと思います。これは、符号なし int である .two の型ではなく、ASCII 数値をバイト配列で表しているためです。

値の数は別として、構造体ポインターを使用して配列内のデータにアクセスした場合、バイト配列の途中にパディングするものがないため、データに正しくアクセスできると思います。したがって、データは連続しており、フィールドは整列しています。アライメントの問題はありません。

memcpy はバイトをコピーするだけであり、構造体のフィールドや、コンパイラーが構造体を整列させるために行った可能性があることを解釈しません。

このようなことをすることが、特にアセンブリ言語での作業で、ポインターを理解できる唯一の方法でした。

于 2012-07-23T23:53:48.330 に答える