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

当我使用一些看起来像的代码时,编译器会产生这个警告。

.... 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

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


说明:

警告原因

  • 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将指向基类的指针static_cast转换为指向派生类的指针基本相同 – 在一般情况下它是不安全的,但如果你知道外部的正确动态类型,它就是明确定义的。

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

clang ++(g ++在任何情况下都不会发出警告)如果使用reinterpret_cast代替c样式转换(你不应该使用它),它不会发出警告,尽管两者都有(在这种情况下)相同的function。 也许是因为reinterpret_cast明确告诉编译器: “相信我,我知道,我在做什么”


旁边注意:在c ++代码中,您不需要struct关键字。

嗯 – -Weverything发出很多警告,其中一些警告会发出不必要的警告。

在这里,您的代码会触发强制显示的cast-align警告

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

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

但是 (以及getaddrinfo的程序员……)知道指针p->ai_addr已经指向一个实际的struct addr_in ,所以转换是有效的。

你可以:

  • 让警告开火并忽略它 – 毕竟它只是一个警告……
  • -Weverything之后用-Wno-cast-align -Weverything

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


或者,如果您知道仅使用CLang,则可以使用编译指示来仅在这些行上发出警告:

 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 .... } 

详细说明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上的端口有关

如何在arm上将sockaddr指针强制转换为sockaddr_in

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