严格别名并通过char *写入int

在一个旧程序中,我将数据结构序列化为字节,通过分配一个unsigned char数组,然后通过以下方式转换为int:

*((*int)p) = value; 

(其中punsigned char*value是要存储的值)。

这工作正常,除非在Sparc上编译,因为访问内存时出现exception对齐而引发exception。 这是完全合理的,因为数据元素具有不同的大小,因此p很快变得不对齐,并在用于存储int值时触发错误,其中底层Sparc指令需要对齐。

这很快得到修复(通过逐字节写出char-array的值)。 但我对此有点担心,因为多年来我在许多程序中都使用过这种结构而没有问题。 但显然我违反了一些C规则(严格别名?),虽然很容易发现这种情况,但是由于优化编译器等原因,违规可能导致其他类型的未定义行为更加微妙。我也有点疑惑,因为我相信多年来我在很多C代码中都看到了这样的结构。 我正在考虑硬件驱动程序,它描述了由硬件交换的数据结构(当然使用pack(1)),并将它们写入h / w寄存器等。所以它似乎是一种常见的技术。

所以我的问题是,上面违反了什么规则,以及实现用例的正确C方法(即将数据序列化为unsigned char数组)。 当然,可以为所有函数编写自定义序列化函数,以逐字节写出它,但听起来很麻烦而且效率不高。

最后,通常可以通过违反此别名规则来预期不良影响(对齐问题等)?

是的,您的代码违反了严格的别名规则 。 在C中,只假设char*及其有signedunsigned对应项为其他类型的别名。

因此,执行此类原始序列化的正确方法是在ints上创建一个数组,然后将其视为unsigned char缓冲区。

 int arr[] = { 1, 2, 3, 4, 5 }; unsigned char* rawData = (unsigned char*)arr; 

您可以memcpyfwrite或执行rawData其他序列化,它绝对有效。

反序列化代码可能如下所示:

 int* arr = (int*)calloc(5, sizeof(int)); memcpy(arr, rawData, 5 * sizeof(int)); 

当然,您应该关注endiannesspadding和其他问题以实现可靠的序列化。

它是编译器和平台特定的,关于如何在内存中表示(布局)结构以及结构的起始地址是否与1,2,4,8,…字节边界对齐。 因此,您不应对结构成员的布局进行任何假设。

在您的成员类型需要特定对齐的平台上,填充字节被添加到结构(这等于我上面的语句,sizeof(struct Foo)> =其数据成员大小的总和)。 填充……

现在,如果你将fwrite()或) memcpy()一个结构从一个实例转换到另一个实例,在具有相同编译器和设置的同一台机器上(例如在你的同一个程序中),你将同时写入数据内容和填充字节,由编译器添加。 只要你处理整个结构,你就可以成功地往返(只要结构中没有指针成员,至少)。

您不能假设的是,您可以将较小的类型(例如unsigned char )转换为“较大类型”(例如unsigned int )和那些方向之间的memcpy,因为unsigned int可能需要在该目标平台上进行正确的对齐。 通常如果你做错了,你会看到总线错误或类似错误。

malloc()在最常见的情况下是为任何类型的数据获取堆内存的通用方法。 无论是字节数组还是某些结构,都与其对齐要求无关。 没有系统存在,您无法struct Foo *ps = malloc(sizeof(struct Foo)) 。 在对齐至关重要的平台上,malloc不会返回未对齐的地址,因为它会破坏任何代码,尝试为结构分配内存。 由于malloc()不是通灵的,如果你用它来分配字节数组,它也会返回“struct compatible aligned”指针。

任何forms的“临时”序列化(如编写整个结构)只是一种很有前景的方法,只要您不需要将序列化数据与其他机器或其他应用程序(或同一应用程序的未来版本,有些人可能使用编译器设置进行修改)进行交换,与对齐有关)。

如果您寻找可移植且更可靠,更强大的解决方案,则应考虑使用其中一个主流序列化软件包,其中一个是上述Google协议缓冲区。