【问题标题】:Why would casting a from an array to a pointer mangle my data?为什么将 a 从数组转换为指针会破坏我的数据?
【发布时间】:2014-03-27 09:22:40
【问题描述】:

前几天我的一个朋友在 Facebook 上发布了这个问题,而我这辈子都想不通。他正在使用 cubesat 协议编写客户端和服务器。由于某种原因,当他将协议结构的数据成员强制转换为指针时,他的数据似乎被破坏了。

客户端代码sn-p:

uint32_t data[3] = { 1234U, 5678U, 9101U };
memcpy(packet->data32, data, sizeof(data));
packet->length = sizeof(data);
csp_send(connection, packet, 1000);

服务器代码sn-p:

uint32_t *data = (uint32_t *)(packet->data32);
printf("Packet received on %i: %u\r\n", PORT, data[0]);
printf("Packet received on %i: %u\r\n", PORT, data[1]);
printf("Packet received on %i: %u\r\n", PORT, data[2]);
printf("Packet received on %i: %u, %u, %u\r\n", PORT, data[0], data[1], data[2]);

输出此代码产生的结果:

Packet received on 15: 2182284498
Packet received on 15: 5678
Packet received on 15: 9101
Packet received on 15: 80904723, 372113408, 596443136

输出此代码的普通读者会期望:

Packet received on 15: 1234
Packet received on 15: 5678
Packet received on 15: 9101
Packet received on 15: 1234, 5678, 9101

经过一番摆弄后,他告诉我,如果他不将结构的 data32 成员转换为 uint32_t*,他会得到正确的输出。

根据我自己的研究,packet 的类型为csp_packet_t,其定义为:

typedef struct __attribute__((__packed__)) {
        uint8_t padding[CSP_PADDING_BYTES];     // Interface dependent padding
        uint16_t length;                        // Length field must be just before CSP ID
        csp_id_t id;                            // CSP id must be just before data
        union {
                uint8_t data[0];                // This just points to the rest of the buffer, without a size indication.
                uint16_t data16[0];             // The data 16 and 32 types makes it easy to reference an integer (properly aligned)
                uint32_t data32[0];             // - without the compiler warning about strict aliasing rules.
        };
} csp_packet_t;

完整的头文件是here

这是 GNU C,所以zero-length arrays are allowed

我不知道两边架构的字长或字节序。

所以,简单地说 - 这里发生了什么?为什么演员阵容很重要?

【问题讨论】:

  • 澄清一下。如果你只是用uint32_t *data = packet->data32; 替换你原来的服务器第一行,那么它就开始工作了吗??
  • 不,他完全删除了该行并将data[...] 替换为packet->data32[...],现在我这么说,让我几乎可以肯定这是因为联合未对齐。
  • 您的意思是编译器(和标准 C?)保证对 struct->arr[k] 的非对齐访问,但不能直接对指针间接访问。那将是合理的行为。
  • 是的,正是如此——请参阅下面我自己的答案。
  • 标准 C 从不保证任何情况下的非对齐访问

标签: c gcc struct cubesat-protocol


【解决方案1】:

21822844980x821304D2,其中0x04d21234,其余的可能是数据包数据。更多,不知道csp_send 和相应的接收者看起来如何(即:显示更多代码)是无法分辨的。

这一行:memcpy(packet->data32, data, sizeof(data)); 实际上是缓冲区溢出错误...因为您没有为packet->data32 分配足够的字节

【讨论】:

  • 零长度数组的想法是用户已经分配了额外的内存。因此,如果我们假设他做对了,sizeof(data) 就可以(我也假设csp_send 希望packet->length 是正文的大小,而不是正文+标题的大小)。但是这些猜测都不能解释为什么摆脱类型转换会有所帮助。
  • 我同意这不是一套完整的代码,这很烦人,但我认为这里已经足够回答它了。关于缓冲区溢出,发生了一个未显示的正确 malloc。
  • 本站有太多完整的代码集。我很高兴看到执行摘要,我们可以随时在 cmets 中要求澄清。
【解决方案2】:

我认为这是一个对齐问题。

引用fritzone:

2182284498 是 0x821304D2 其中 0x04d2 是 1234 其余可能是数据包数据

这是因为联合是打包结构的成员,它有 16 位不对齐。从打包结构中的联合成员访问时,编译器会使用一些魔法来确保正确检索打包(未对齐)数据。但是,当转换为 uint32_t* 时,编译器会丢失打包细节,我相信它假定它正在访问正确对齐的数据。

【讨论】:

  • 可能地址本身不是字对齐的,但是 CPU 获取操作做了一些邪恶的事情。对于 exanoke x86,即使 %eax 最终被使用,也可能会获取 %ax。然后,如果高字节以某种方式被清除,则数组的其余部分似乎可以工作(至少内容小于 2^16)。
  • 我想我会在几天内不接受我的答案,以防有人出现并希望写一个更详细的解释。我相信它是正确的,但我不认为说“对齐问题”和“未定义的行为”就像有人可以详细说明编译器/机器在这种情况下实际做了什么一样好。
【解决方案3】:

Arrays cannot have size zero。此代码使用一些非标准 C 甚至使联合编译。所以在那个工会内部发生了什么,没人知道。就 C 语言而言,这是未定义的行为。

由于程序在遇到未定义的行为时会执行“任何操作”,因此可以按预期工作。

【讨论】:

  • 我相信他们可以从 C99 开始?
  • @BenBurns No. C99 引入了一个定义良好、灵活的数组成员作为结构/联合的最后一个元素,但它们没有以这种方式声明。
  • 抱歉,我在考虑一个灵活的数组。 gcc.gnu.org/onlinedocs/gcc/Zero-Length.html
  • 我应该提到 gcc 是编译器 - 我将更新问题并添加指向完整头文件的链接。
  • @BenBurns Some info 关于灵活数组成员和 GCC 非标准扩展。这似乎很可能是怪事的根源。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-04
相关资源
最近更新 更多