0

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

*((*int)p) = value;

(其中,p是要存储的值)。unsigned char*value

这工作正常,除非在 Sparc 上编译时由于访问内存对齐不正确而触发异常。这很有意义,因为具有不同大小的数据元素p很快就会变得不对齐,并在用于存储 int 值时触发错误,其中底层 Sparc 指令需要对齐。

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

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

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

4

2 回答 2

1

是的,您的代码违反了严格的别名规则。在 C 中,只有char*和它的signed对应unsigned物被假定为其他类型的别名。

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

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

你可以memcpy, fwrite, 或者做其他的序列化rawData,绝对有效。

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

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

当然,你应该关心endianness,padding和其他问题来实现可靠的序列化。

于 2015-09-28T21:45:48.280 回答
0

它是编译器和平台特定的,关于如何在内存中表示(布局)结构以及结构的起始地址是否与 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()不是通灵的,如果您使用它来分配字节数组,它还将返回“结构兼容对齐”指针。

只要您不需要与其他机器或其他应用程序(或同一应用程序的未来版本,其中有人可能已经修改了编译器设置,与对齐有关)。

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

于 2015-09-28T21:15:14.693 回答