【问题标题】:When is char* safe for strict pointer aliasing?对于严格的指针别名,char* 何时安全?
【发布时间】:2010-09-20 17:09:01
【问题描述】:

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

Here 声明如下:

始终假定 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 数组并且我将其转换为结构,这第二个示例会不起作用吗?在严格别名的世界中,您如何处理这种情况?

【问题讨论】:

  • 第二段代码不是要求你明确地 casr 吗?您是否启用了所有警告?

标签: c sockets strict-aliasing


【解决方案1】:

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

退后一步弄清楚这是怎么回事可能会很有用。

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

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

char* 呢?

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

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

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

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

【讨论】:

  • 都是charsigned charunsigned char 中的3 个@ 别名可以吗?以及任何 CV 资格组合?
  • 别名规则与对齐无关。根据 C89 的基本原理,给定像 int i; float *fp; 这样的全局声明,其目的是允许编译器在访问 *fp 时将 i 保存在寄存器中。这个想法是编译器不应该悲观地假设对*fp 的写入可能会改变i 当它没有理由期望 *fp 会指向一些不是' float*。我不认为该规则旨在让编译器忽略别名明显的情况(获取对象的地址应该给编译器一个强有力的线索......
  • ...有问题的对象即将通过指针访问,并将int* 转换为float* 应该给编译器一个强有力的线索,即int 可能通过写信到float* 进行修改,但 gcc 不再觉得有义务注意到这些事情。
  • 严格的别名不是因为对齐要求。这个答案怎么能得到 9 票?
【解决方案2】:

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

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

recv(socket, msgBuff, 100);

printf("Got Msg: a: %i, b: %i", msg.a, msg.b);

【讨论】:

  • 这是否符合标准,或者只是编译器让您摆脱对一个成员的写入和另一个成员的读取?
  • 联合是完全没有必要的。只需将指向结构的指针(转换为char *)传递给recv
  • 请注意,-fstrict-aliasing 在 gcc 中默认为 -O2 及更高版本
  • @R..GitHubSTOPHELPINGICE 你能详细说明一下吗?
  • 这个答案有很多问题。 (1) recv 有四个参数。 (2) 这里不需要通过联合进行类型双关。 (3) 但我们也不需要强制转换,这与之前的评论所说的相反。 (4) 事实上,常规用法就像SocketMsgToRecv msg; ssize_t ret = recv(socket, &msg, sizeof msg, flags); 一样简单——当然我们总是需要处理错误。 (我知道这是一个旧答案,但它是 Google 上的热门答案之一。)
猜你喜欢
  • 2014-07-13
  • 2023-02-02
  • 1970-01-01
  • 1970-01-01
  • 2014-07-26
  • 1970-01-01
  • 1970-01-01
  • 2016-10-09
相关资源
最近更新 更多