我有一个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
的问题。我该如何解决这个问题?
答案 0 :(得分:5)
有一些问题:
出于效率原因,编译器对齐变量的边界等于处理器的寄存器大小。即在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或attribute指令覆盖此行为。 使用gcc,您可以使用属性更改结构定义。例如。更改上面的代码以添加“打包”属性(需要gcc):
typedef struct __attribute__((packed))
然后再次运行程序。尺寸现在应该是相同的。 注意:在某些处理器架构上,例如ARMv4,32位变量必须在32位boudary上对齐,否则您的程序将无法运行(获得异常)。 阅读“对齐”和“打包”编译指示或属性的编译器文档。
下一个问题是字节顺序。试试这个:
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来打印unsigned int。
复制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;
}
最后,依赖打包数据结构通常是一个坏主意,因为它使得将代码移植到不同的平台变得困难。但是,有时您需要打包/解包协议包。在这些特殊情况下,对于每种数据类型,使用一对函数手动打包/解压缩每个项目通常是最好的和最便携的。
我将留下关于另一个主题的endian-ness问题。 : - )
答案 1 :(得分:0)
正如Mysticial已经解释过的那样,你所看到的是structure alignment的影响 - 编译器会在字大小的边界上对齐元素,即在4字节边界上的32位代码中,有效地留下间隙char [5]和下一个元素之间的3个字节。
如果你使用gcc或Visual Studio,#pragma pack(1)
允许你覆盖编译器默认使用的“首选”包装 - 在这个例子中,你指示编译器指示1字节边界,即没有“孔”。这在嵌入式系统中通常可用于将字节块映射到结构上。有关其他编译器,请参阅编译器手册。
答案 2 :(得分:0)
正如您的其他答案已经表明的那样,您会看到对齐问题。编译器倾向于根据您拥有的处理器类型,沿着长边或四字边界对齐数据结构。这意味着如果你在结构中声明的内容没有对齐,那么编译器会以对齐字节打包,你不应该看到它们。
顺便说一句,从前,整个世界都不是英特尔;还有其他处理器,每个处理器都有自己独特的对齐要求,所以我们都处理了很多对齐,特别是在不同的处理器系列中移植引导ROM代码。
遇到这样的问题时,我建议您更改代码以进行一些实验,如下所示:
1)在代码中添加声明structure * pStructure;
。
2)添加pStructure =(structure *)数组; `在数组声明之后。
3)然后,在memcpy所在的行,设置一个断点。
当你点击断点时,输入print或display命令(gdb使用p)
p pStructure->one
(gdb) p pStructure->one
$4 = "ABCD"
然后是以下
(gdb) p pStructure->two
$7 = 3486515278
对于4字节的数字,我相信你没有看到你期望的数字,因为你表示一个ASCII数字是一个字节数组而不是.two的类型是unsigned int。
除了值的数量,如果你使用结构指针访问数组中的数据,我相信会正确访问数据,因为在字节数组的中间没有任何内容可以填充。因此,您的数据是连续的,并且您的字段排成一行。没有对齐问题。
memcpy只是复制字节,并不解释结构的字段或编译器可能为了对齐结构而做的事情。
做这样的事情是我能够理解指针的唯一方法,尤其是使用汇编语言。