我一直在尝试理解严格的别名规则,因为它们适用于char指针。
Here说明了这一点:
始终假定char *可以引用任何对象的别名。
好的,在socket代码的上下文中,我可以这样做:
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数组,我将它转换为结构体?你如何在一个严格别名的世界中处理这种情况?
答案 0 :(得分:8)
回复@Adam Rosenfield:只要char *的供应商开始做类似的事情,工会就会实现一致。
退后一步,弄清楚这是什么,这可能是有用的。
别名规则的基础是编译器可以将不同简单类型的值放在不同的内存边界上以改善访问,并且在某些情况下硬件可能需要这样的对齐才能够使用指针。这也可以显示在具有各种不同大小元素的结构中。结构可以在良好的边界上开始。此外,编译器仍然可以在结构内部引入松弛咬合,以实现需要它的struct元素的正确对齐。
考虑到编译器通常可以选择控制所有这些是如何处理的,您可以看到有很多方法可以发生意外。当将指针传递给结构体(无论是否为char *)时,注意到编译为期望不同对齐约定的库时,这一点尤其重要。
char *怎么样?
关于char *的假设是sizeof(char)== 1(相对于所有其他大小数据的大小),并且char *指针没有任何对齐要求。因此,真正的char *总是可以安全地传递并成功使用而无需考虑对齐,这适用于char []数组的任何元素,在指针上执行++和 - 等等。 (奇怪的是,无效*并不完全相同。)
现在你应该能够看到如何将某种结构数据转移到一个本身没有正确对齐的char []数组中,试图转换回一个需要对齐的指针可能是一个严重的问题
如果你建立一个char []数组和一个结构的联合,编译器将尊重要求最高的对齐(即结构的对齐)。如果供应商和消费者正在有效地使用匹配的联合,以便将struct *转换为char *并返回正常工作,这将起作用。
在这种情况下,我希望数据是在类似的联合中创建的,然后指向它的指针被转换为char *,或者它以任何其他方式作为sizeof(char)字节数组传输。确保所依赖的库与您自己的代码之间的任何编译器选项兼容也很重要。
答案 1 :(得分:6)
正确,第二个示例违反了严格的别名规则,因此如果使用-fstrict-aliasing
标志进行编译,则可能会出现错误的对象代码。完全正确的解决方案是在这里使用联合:
union
{
SocketMsgToRecv msg;
char msgBuff[100];
};
recv(socket, msgBuff, 100);
printf("Got Msg: a: %i, b: %i", msg.a, msg.b);