何时char *对于严格的指针别名是安全的?

我一直在尝试理解严格的别名规则,因为它们适用于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数组,我将它转换为结构体吗? 你如何在一个严格别名的世界中处理这种情况?

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

回顾并弄清楚这是什么一点可能是有用的。

别名规则的基础是编译器可以将不同简单类型的值放在不同的存储器边界上以改善访问,并且在某些情况下硬件可能需要这种对齐以便能够使用指针。 这也可以显示在具有各种不同大小元素的结构中。 结构可以在良好的边界上开始。 此外,编译器仍然可以在结构内部引入松弛咬合,以实现需要它的结构元素的正确对齐。

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

char *怎么样?

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

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

如果你创建一个char []数组和一个结构的联合,那么编译器将尊重要求最高的对齐(即结构的对齐)。 如果供应商和消费者有效地使用匹配的联合,以便将struct *转换为char *并返回工作正常,这将起作用。

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

更正,第二个示例违反了严格的别名规则,因此如果使用-fstrict-aliasing标志进行编译,则可能会出现错误的对象代码。 完全正确的解决方案是在这里使用union:

 union { SocketMsgToRecv msg; char msgBuff[100]; }; recv(socket, msgBuff, 100); printf("Got Msg: a: %i, b: %i", msg.a, msg.b);