27

我一直在尝试理解严格的别名规则,因为它们适用于 char 指针。

这里说明:

始终假定 char* 可以引用任何对象的别名。

好的,所以在套接字代码的上下文中,我可以这样做:

struct SocketMsg
{
   int a;
   int b;
};

int main(int argc, char** argv)
{
   // Some code...
   SocketMsg msgToSend;
   msgToSend.a = 0;
   msgToSend.b = 1;
   send(socket, (char*)(&msgToSend), sizeof(msgToSend);
};

但是有这样的声明

反之则不成立。将 char* 转换为除 char* 之外的任何类型的指针并取消引用它通常违反严格的别名规则。

这是否意味着当我接收一个 char 数组时,当我知道消息的结构时,我无法重新解释转换为结构:

struct SocketMsgToRecv
{
    int a;
    int b;
};

int main()
{
    SocketMsgToRecv* pointerToMsg;
    char msgBuff[100];
    ...
    recv(socket, msgBuff, 100);
    // Ommiting make sure we have a complete message from the stream
    // but lets assume msgBuff[0]  has a complete msg, and lets interpret the msg

    // SAFE!?!?!?
    pointerToMsg = &msgBuff[0];

    printf("Got Msg: a: %i, b: %i", pointerToMsg->a, pointerToMsg->b);
}

由于基类型是 char 数组并且我将其转换为结构,因此第二个示例不起作用吗?在一个严格别名的世界里,你如何处理这种情况?

4

2 回答 2

6

回复@Adam Rosenfield:只要 char* 的供应商开始做类似的事情,工会就会实现对齐。

退后一步弄清楚这一切可能是有用的。

别名规则的基础是编译器可以将不同简单类型的值放在不同的内存边界上以改进访问,并且硬件在某些情况下可能需要这种对齐才能完全使用指针。这也可以出现在有各种不同大小元素的结构中。该结构可以在良好的边界上开始。此外,编译器可能仍会在结构内部引入松弛位,以完成需要它的结构元素的正确对齐。

考虑到编译器通常具有控制如何处理所有这些的选项,您可以看到有很多方式可能会发生意外。在将指向结构的指针(转换为 char* 或不转换为 char* 或不转换为)传递到编译为期望不同对齐约定的库时,这一点尤为重要。

char* 呢?

关于 char* 的假设是 sizeof(char) == 1 (相对于所有其他相当大的数据的大小)并且 char* 指针没有任何对齐要求。因此,真正的 char* 始终可以安全地传递并成功使用,而无需担心对齐,这适用于 char[] 数组的任何元素,在指针上执行 ++ 和 -- 等等。(奇怪的是, void* 并不完全相同。)

现在您应该能够看到,如果将某种结构数据传输到本身未正确对齐的 char[] 数组中,尝试强制转换回需要对齐的指针可能是一个严重的问题。

如果您将 char[] 数组和结构合并,编译器将遵循最苛刻的对齐方式(即结构的对齐方式)。如果供应商和消费者有效地使用匹配联合,这将起作用,以便将 struct* 转换为 char* 并返回工作正常。

在这种情况下,我希望数据在指向它的指针被强制转换为 char* 或以任何其他方式作为 sizeof(char) 字节数组传输之前在类似的联合中创建。确保任何编译器选项在所依赖的库和您自己的代码之间兼容也很重要。

于 2008-11-04T20:19:04.710 回答
4

正确,第二个例子违反了严格的别名规则,所以如果你用这个-fstrict-aliasing标志编译,你可能会得到不正确的目标代码。完全正确的解决方案是在这里使用联合:

union
{
  SocketMsgToRecv msg;
  char msgBuff[100];
};

recv(socket, msgBuff, 100);

printf("Got Msg: a: %i, b: %i", msg.a, msg.b);
于 2008-11-04T16:37:48.930 回答