从sockaddr *转换为sockaddr_in *增加了所需的对齐

时间:2016-02-22 10:59:34

标签: c++ linux networking casting clang

当我使用一些看起来像 -

的代码时,编译器会发出此警告
....

for(p = res; p != NULL; p = p->ai_next) {
    void *addr;
    std::string ipVer = "IPv0";

    if(p->ai_family == AF_INET) {
        ipVer                    = "IPv4";
        struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
        addr                     = &(ipv4->sin_addr);
    }

    else {
        ipVer                     = "IPv6";
        struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
        addr                      = &(ipv6->sin6_addr);
    }
....
}

其中p = res的类型为struct addrinfo,产生警告的类型为sockaddr_insockaddr_in6。警告来自声明:

  • struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
  • struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;

我想知道的是导致此警告是什么以及我可以做什么来纠正它,如果这不是正确的做事方式。我可以在这里使用static_cast / dynamic_cast / reinterpret_cast中的任何一个吗?

确切的警告是 - cast from 'struct sockaddr *' to 'struct sockaddr_in *' increases required alignment from 2 to 4

3 个答案:

答案 0 :(得分:7)

TLDR:此警告并未表示代码中存在错误,但您可以使用poper c ++ reinterpret_cast来避免错误(感谢@Kurt Stutsman)。< / p>

<强>解释

警告原因

  • sockaddr由无符号短(通常为16位)和char数组组成,因此其对齐要求为2.
  • sockaddr_in包含(除其他外)一个struct in_addr,其对齐要求为4,这反过来意味着sockaddr_in也必须与4字节边界对齐。

因此,将sockaddr*转换为sockaddr_in*会更改对齐要求,并且通过新指针访问对象甚至会违反别名规则并导致未定义的行为。

为什么你可以忽略它

在您的情况下,对象p->ai_addr指向,最有可能是sockaddr_insockaddr_in6对象(通过检查ai_family确定),因此操作安全。但是,编译器并不知道并产生警告。

与使用static_cast将指向基类的指针强制转换为指向派生类的指针基本相同 - 在一般情况下它是不安全的,但如果你知道正确的动态类型是外在的,它定义明确。

<强>解决方案:
我不知道一个干净的方法(除了抑制警告),这在-Weverything启用警告时并不罕见。您可以将p->ai_addr逐字节指向的对象复制到相应类型的对象,但随后您可能(很可能)不再像以前那样使用addr,就像现在一样指向不同的(例如本地的)变量。
-Weverything不是我用于常规构建的东西,因为它会增加太多的噪音,但如果你想保留它,@ Kurt Stutsman在评论中提到了一个很好的解决方案:

clang ++(g ++在任何情况下都不会发出警告)如果使用reinterpret_cast代替c样式演员(你不应该这样做),它不会发出警告尽管如此,虽然两者都有(在这种情况下)完全相同的功能。也许是因为reinterpret_cast明确告诉编译器:&#34;相信我,我知道,我在做什么&#34;

另一方面注意:在c ++代码中,您不需要struct个关键字。

答案 1 :(得分:6)

-Weverything启用了很多警告,其中一些警告会发出不必要的警告。

此处您的代码会触发cast-align警告,明确说明

  

从...转换为......从...增加所需的对齐...

这就是这种情况,因为struct addr的对齐只有2,而struct addr_in则为4。

但是(以及getaddrinfo的程序员......)知道指针p->ai_addr已经指向实际的struct addr_in,所以演员阵容是有效的。

你可以:

  • 让警告开火并忽略它 - 毕竟它只是一个警告......
  • -Wno-cast-align
  • 之后使用-Weverything将其静音

我必须承认,由于这个原因我很少使用-Weverything,并且只使用-Wall

或者,如果您知道自己只使用CLang,则可以使用pragmas在这些行上轻松地发出警告

for(p = res; p != NULL; p = p->ai_next) {
    void *addr;
    std::string ipVer = "IPv0";

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wcast-align"

    if(p->ai_family == AF_INET) {
        ipVer                    = "IPv4";
        struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
        addr                     = &(ipv4->sin_addr);
    }

    else {
        ipVer                     = "IPv6";
        struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
        addr                      = &(ipv6->sin6_addr);
    }
#pragma clang diagnostic pop

....
}

答案 2 :(得分:1)

详细说明memcpy版本。我认为这是ARM所必需的,它不能有错误的数据。

我创建了一个只包含前两个字段的结构(我只需要端口)

struct sockaddr_in_header {
    sa_family_t    sin_family; /* address family: AF_INET */
    in_port_t      sin_port;   /* port in network byte order */
};

然后为了获得端口,我使用memcpy将数据移动到堆栈

struct sockaddr_in_header   sinh;
unsigned short              sin_port;

memcpy(&sinh, conn->local_sockaddr, sizeof(struct sockaddr_in_header));

返回端口

sin_port = ntohs(sinh.sin_port);

这个答案与获取Arm上的端口非常相关

How do I cast sockaddr pointer to sockaddr_in on Arm

认为与这个问题是同一个问题的权力,但是我不想忽视警告。经验告诉我这是一个坏主意。