C语言 严格混叠和通过char*写入int



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

*((*int)p) = value;

(其中punsigned char*, value为待存储值)。

这工作得很好,除了在Sparc上编译时,由于访问内存不正确的对齐而触发异常。这是完全有意义的,因为数据元素有不同的大小,所以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;

您可以对memcpyfwriterawData进行其他序列化,并且它是绝对有效的。

反序列化代码可能像这样:

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

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

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

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

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

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

在大多数情况下,

malloc()是为任何类型的数据获取堆内存的通用方法。它是一个字节数组或某种结构体,独立于其对齐要求。没有系统存在,你不能struct Foo *ps = malloc(sizeof(struct Foo))。在对齐至关重要的平台上,malloc不会返回未对齐的地址,因为它会破坏任何代码,试图为结构分配内存。由于malloc()不是通灵的,如果使用它来分配字节数组,它还将返回"结构兼容对齐"指针。

只要不需要与其他机器或其他应用程序(或同一应用程序的未来版本,其中可能有人修改了与对齐相关的编译器设置)交换序列化数据,任何形式的"临时"序列化(如编写整个结构)都是一种有前途的方法。

如果您正在寻找一种可移植的、更可靠的、健壮的解决方案,您应该考虑使用主流序列化包之一,其中之一就是前面提到的Google协议缓冲区。

相关内容

  • 没有找到相关文章

最新更新