4

我正在用 C 语言在 ARM9 处理器上为 Linux 编写程序。该程序将访问包含一系列标记数据的网络数据包,例如:

<fieldID><length><data><fieldID><length><data> ...

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

只要<data>字节数是偶数,我看不出问题。但是如果我有一个 1 或 3 或 5 字节<data>的部分,那么下一个 16 位字段 ID 最终不会在 16 位边界上,并且我预计会出现对齐问题。自从我从头开始做这样的事情已经有一段时间了,所以我有点不确定细节。欢迎任何反馈。谢谢。

4

4 回答 4

6

为避免在这种情况下出现对齐问题,请以unsigned char *. 所以:

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

上面的示例假定“小端”数据布局,其中最低有效字节在多字节数中排在第一位。

于 2009-02-09T18:46:42.940 回答
4

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

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;
}
于 2009-02-09T19:22:02.463 回答
4

简单的方法是手动重建uint16_ts,以牺牲速度为代价:

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;

如果您的处理器支持它,您可以键入双关或使用联合(但要注意严格的别名)。

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,因为它是最便携的。

于 2009-02-09T18:53:41.910 回答
1

如果您通过字节指针,对齐总是会很好,尽管可能不是超级有效。

撇开字节序问题不谈,您可以从“真实”字节指针中 memcpy 到正确对齐的任何您想要/需要的内容中,您会没事的。

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

于 2009-02-09T18:47:25.667 回答