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.two包含 1128415566 而我期望 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,您可以使用属性更改结构定义。例如,更改上面的代码以添加“打包”属性(需要 gcc):

typedef struct __attribute__((packed))

然后再次运行程序。现在大小应该是一样的。 注意:在某些处理器架构上,例如 ARMv4,32 位变量必须在 32 位边界上对齐,否则您的程序将无法运行(出现异常)。阅读“对齐”和“打包”编译指示或属性的编译器文档。

下一个问题是字节顺序。试试这个:

printf("0x%08X\n", 12345678);

十六进制的 12345678 是 0x00BC614E。从您的示例和您获得的输出中,我可以看出您的平台是“小端”。在“小端”系统中,数字0x00BC614E存储为从最低有效字节开始的字节序列,例如0x4E, 0x61, 0xBC, 0x00. 所以改变你的数组定义:

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

现在您的程序将打印 12345678。

另请注意,您应该使用 %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 位代码,有效地在 char 之间留下 3 个字节的间隙[5] 和下一个元素。

如果您使用 gcc 或 Visual Studio,#pragma pack(1)则允许您覆盖编译器默认使用的“首选”打包 - 在此示例中,您指示编译器在 1 字节边界上进行指示,即没有“孔”。这在嵌入式系统中通常很有用,可以将字节块映射到结构上。对于其他编译器,请参阅您的编译器手册。

于 2012-07-23T23:21:31.333 回答
0

正如您的其他答案已经表明的那样,您看到了对齐问题。编译器倾向于根据您拥有的处理器类型沿长或四字边界对齐数据结构。这意味着如果您在结构中声明的内容不对齐,那么编译器会将对齐字节打包,并且您不应该看到它们。

顺便说一句,从前,整个世界都不是英特尔,而是英特尔。还有其他处理器,每个处理器都有自己独特的对齐要求,所以对齐是我们都处理的相当多的事情,尤其是在不同处理器系列之间移植引导 ROM 代码。

当遇到这样的问题时,我建议修改你的代码来做一个小实验,如下所示:

structure * pStructure;1)在您的代码中添加声明。

2)添加pStructure =(结构*)数组;` 在数组声明之后。

3)然后,在memcpy所在的那一行,设置一个断点。

当你命中断点时,输入打印或显示命令(gdb 使用 p)

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

然后是以下

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

至于 4 字节数字,我相信您没有看到您期望的数字,因为您表示的是一个 ASCII 数字,一个字节数组,而不是 .two 的无符号整数类型。

除了值的数量,如果你使用结构指针访问数组中的数据,我相信会正确访问数据,因为在字节数组中间没有什么可以填充的。因此,您的数据是连续的,并且您的字段排列整齐。不存在对齐问题。

memcpy 只是复制字节,不会解释结构的字段或编译器可能为对齐结构所做的工作。

做这样的事情是我欣赏指针的唯一方法,尤其是在汇编语言中工作。

于 2012-07-23T23:53:48.330 回答