从C访问网络数据包中未对齐数据的安全有效方法

我正在用ARM9处理器在C for Linux上编写程序。 该程序用于访问网络数据包,其中包括一系列标记数据,如:

 ... 

fieldID和length字段都是uint16_t。 数据可以是1个或更多字节(如果使用全长,则最多64k,但事实并非如此)。

只要具有偶数个字节,我就不会发现问题。 但是如果我有一个1或3或5字节部分,那么下一个16位fieldID最终不会出现在16位边界上,我预计会出现对齐问题。 已经有一段时间了,因为我从头做过这样的事情,所以我对细节不太了解。 任何反馈欢迎。 谢谢。

为了避免在这种情况下出现对齐问题,请将所有数据作为unsigned char * 。 所以:

 unsigned char *p; //... uint16_t id = p[0] | (p[1] << 8); p += 2; 

上面的示例假定为“little endian”数据布局,其中最低有效字节首先出现在多字节数字中。

您应该具有函数(如果您使用的语言支持这些function,则为内联和/或模板化),这些函数将读取可能未对齐的数据并返回您感兴趣的数据类型。例如:

 uint16_t unaligned_uint16( void* p) { // this assumes big-endian values in data stream // (which is common, but not universal in network // communications) - this may or may not be // appropriate in your case unsigned char* pByte = (unsigned char*) p; uint16_t val = (pByte[0] << 8) | pByte[1]; return val; } 

简单的方法是以速度为代价手动重建uint16_t ;

 uint8_t *packet = ...; uint16_t fieldID = (packet[0] << 8) | packet[1]; // assumes big-endian host order uint16_t length = (packet[2] << 8) | packet[2]; uint8_t *data = packet + 4; packet += 4 + length; 

如果你的处理器支持它,你可以输入pun或者使用union(但要注意严格的别名 )。

 uint16_t fieldID = htons(*(uint16_t *)packet); uint16_t length = htons(*(uint16_t *)(packet + 2)); 

请注意,并不总是支持未对齐访问(例如,它们可能会生成某种类型的错误),而在其他体系结构上,它们受到支持,但性能会受到影响。

如果数据包未对齐,您可以始终将其复制到静态缓冲区中然后读取它:

 static char static_buffer[65540]; memcpy(static_buffer, packet, packet_size); // make sure packet_size <= 65540 uint16_t fieldId = htons(*(uint16_t *)static_buffer); uint16_t length = htons(*(uint16_t *)(static_buffer + 2)); 

就个人而言,我只是选择#1,因为它将是最便携的。

如果你通过一个字节指针,对齐总是很好,虽然可能不是超级高效。

抛开endian-ness的问题,你可以将’真实’字节指针存储到你想要/需要的任何正确对齐的内容中,你就可以了。

(这是有效的,因为生成的代码将数据加载/存储为字节,这是对齐安全的。当生成的程序集有指令以错误对齐的方式加载和存储16/32/64位内存时,它们全部崩溃)。