如何投射sockaddr_storage并避免违反严格别名规则

时间:2009-09-15 21:10:40

标签: c sockets aliasing strict-aliasing

我正在使用Beej的网络指南,并遇到了一个别名问题。他提出了一个函数来返回特定结构的IPv4或IPv6地址:

1  void *get_in_addr( struct sockaddr *sa )
2  {
3      if (sa->sa_family == AF_INET)
4        return &(((struct sockaddr_in*)sa)->sin_addr);
5      else
6        return &(((struct sockaddr_in6*)sa)->sin6_addr);
7  }

这导致GCC在第3行为 sa 吐出严格别名错误。据我所知,这是因为我这样调用这个函数:

struct sockaddr_storage their_addr;
...
inet_ntop(their_addr.ss_family,
          get_in_addr((struct sockaddr *)&their_addr),
          connection_name,
          sizeof connection_name);

我猜测别名与their_addr变量的类型为sockaddr_storage而另一个不同类型的指针指向同一内存这一事实有关。

解决这个问题的最佳方法是sockaddr_storagesockaddr_insockaddr_in6加入联盟吗?看起来这应该是网络中的一个很好的领域,我只是找不到任何有最佳实践的好例子。

此外,如果有人能够准确解释别名问题发生的位置,我会非常感激。

4 个答案:

答案 0 :(得分:21)

我倾向于通过type-punning让GCC做正确的事情,联盟明确允许这样做:


/*! Multi-family socket end-point address. */
typedef union address
{
    struct sockaddr sa;
    struct sockaddr_in sa_in;
    struct sockaddr_in6 sa_in6;
    struct sockaddr_storage sa_stor;
}
address_t;

答案 1 :(得分:1)

  

我倾向于这样做以使GCC使用类型惩罚做正确的事情,这是联盟明确允许的

我很确定这种(错误)使用联盟将与GCC一起工作(或只是偶然):

short type_pun2 (int i, int *pi, short *ps) {
    *pi = i;
    return *ps;
}

union U {
    int i;
    short s;
};

short type_pun (int i) {
    U u;
    return type_pun2 (i, &u.i, &u.s);
}

正确的方法是使用memcpy,而不是union

答案 2 :(得分:0)

我最近在尝试编写代码以获取机器的MAC地址时在HPUX系统上发出了类似的别名警告

&(((struct sockaddr_in *)addr)->sin_addr)抱怨严格别名规则

这是某些上下文中的代码

 char ip[INET6_ADDRSTRLEN] = {0};
 strucut sockaddr *addr

 ...
 get addr from ioctl(socket,SOCGIFCONF...) call
 ...
 inet_ntop(AF_INET, &(((struct sockaddr_in *)addr)->sin_addr),ip,sizeof ip);

我通过执行以下操作克服了别名警告

struct sockaddr_in sin;
memcpy(&sin,addr,sizeof(struct sockaddr));
inet_ntop(AF_INET, &sin.sin_addr,ip,sizeof ip);

虽然这有潜在危险,但我在它之前添加了以下行

 static_assert(sizeof(sockaddr)==sizeof(sockaddr_in));

我不确定这是否会被视为不良做法,但它起作用并且与其他* Nix风格和编译器交叉平台

答案 3 :(得分:-1)

该问题与对函数的调用无关。相反,它与((struct sockaddr_in*)sa)->sin_addr一致。问题是sa是一种类型的指针,但是您将它转换为不同类型的指针然后解除引用它。这打破了一个名为“严格别名”的规则,该规则表明不同类型的变量永远不会混淆。在您的情况下,别名到另一种类型正是您想要做的。

简单的解决方案是关闭此优化,以此方式允许别名。在GCC上,标志为-fno-strict-aliasing

更好的解决方案是使用联盟,如Nikolai所述。

void *get_in_addr(struct sockaddr *sa)
{
    union {
        struct sockaddr     *sa;
        struct sockaddr_in  *sa_in;
        struct sockaddr_in6 *sa_in6;
    } u;
    u.sa = sa;
    if (sa->sa_family == AF_INET)
        return &(u.sa_in->sin_addr);
    else
        return &(u.sa_in6->sin6_addr);
}

那就是说,在使用原始代码时,我实际上无法让GCC给我一个警告,所以我不确定这是否会给你买任何东西。