奇怪的指针行为

时间:2014-05-20 17:03:15

标签: c pointers math types casting

我有一个包含以前从socket读取的数据的缓冲区。 从存储在缓冲区中的信息, 我可以告诉整个数据长度(91字节)和规格, 我知道我需要检索的其他信息的位置(32位整数和16位整数,让我们称之为uid和suid)。

unsigned char buffer[1024];
uint32_t uid;
uint16_t suid;

uid = ntohl ( *((uint32_t*) (buffer + sizeof (struct pktheader))) );

suid = ntohs ( *((uint16_t*) (buffer + sizeof (struct pktheader) + sizeof (uint32_t))) );

此代码是针对ARM进行交叉编译的,并且由于某些意外原因, uid 的内容填充了错误的字节,这些字节是缓冲区的一部分,但是在我想要检索的内容之前存在(!) 。 好像错误地计算了偏移量。 奇怪的是, suid 的内容很好。

你能解释这种行为甚至可能吗?我知道我提供的信息可能很难...我们可以排除sizeof (struct pktheader)的错误值,我已经仔细检查了。缓冲区中的内容如规范中所定义。我甚至找到了一个使用memcpy使用相同偏移计算的工作解决方案来解决每个部分,所以我们几乎可以排除混合数据的可能性。

我和我的同事讨论了这个问题,他的专业猜测是发生了一些自动对齐行为,指出偏移只有2个字节。不过我想知道更多。

到目前为止,我非常喜欢这种结构,以便访问存储在缓冲区中的各个部分。

1 个答案:

答案 0 :(得分:1)

这几乎肯定是@Dark Falcon建议的对齐问题。最有可能的是,CPU忽略了地址的最后两位并执行了对齐的负载。

虽然不支持未对齐的负载似乎是一个奇怪的设计选择,但它主要与门数和功耗有关。有两种令人讨厌的场景,CPU需要应付其他方面:

  • 加载/存储跨越缓存行边界
  • 加载/存储跨越页面边界

这两个都需要大量的门来修复 - 尤其是后者 - 因为内存访问实际上跨越了缓存行页面边界。在大多数ARM部件中,盖茨,房地产和功耗都是非常宝贵的。

此行为也完全兼容C和C ++标准,因此上面的代码不是普遍可移植的。

使用未对齐的访问在缓冲区上覆盖structunion也不安全 - 编译器会将其放置以避免未对齐的访问,并且也不会初始化任何剩余的间隙(所以永远不要使用memcmp来比较它们)。它也正是您不想要的有线格式数据包。

使用结构打包更安全 - 编译器将知道哪些内存访问是允许的,哪些不是,并且将生成更小的读取和写入,以便不执行未对齐的访问。但是,直到最近,启用打包的机制一直是编译器特定的 - 因此也不可移植。

唯一真正可移植的实现选择是逐字节访问(并转换为合并数据)。