cast from sockaddr * to sockaddr_in * increases re

2019-02-13 21:33发布

问题:

The compiler produces this warning when I'm working with some code which looks like -

....

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

where p = res are of type struct addrinfo and the types producing warnings are sockaddr_in and sockaddr_in6. The warning comes from statements :

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

All I want to know is what is causing this warning and what can I do to correct it if this is not the proper way to do things. Could I use any of static_cast / dynamic_cast / reinterpret_cast here?

The exact warning is - cast from 'struct sockaddr *' to 'struct sockaddr_in *' increases required alignment from 2 to 4.

回答1:

TLDR: This warning doesn't indicate an error in your code, but you can avoid it by using a poper c++ reinterpret_cast (thanks to @Kurt Stutsman).


Explanation:

Reason for the warning:

  • sockaddr consists of a unsigned short (usually 16 bit) and a char array, so its alignment requirement is 2.
  • sockaddr_in contains (among other things) a struct in_addr which has an alignment requirement of 4 which in turn means sockaddr_in also must be aligned to a 4 Byte boundary.

For that reason, casting an arbitrary sockaddr* to an sockaddr_in* changes the alignment requirement, and accessing the object via the new pointer would even violate aliasing rules and result in undefined behavior.

Why you can ignore it:

In your case, the object, p->ai_addr is pointing to, most likely is a sockaddr_in or sockaddr_in6 object anyway (as determined by checking ai_family) and so the operation is safe. However you compiler doesn't know that and produces a warning.

It is essentially the same thing as using a static_cast to cast a pointer to a base class to a pointer to a derived class - it is unsafe in the general case, but if you know the correct dynamic type extrinsically, it is well defined.

Solution:
I don't know a clean way around this (other than suppress the warning), which is not unusual with warnings enabled by -Weverything . You could copy the object pointed to by p->ai_addr byte by byte to an object of the appropriate type, but then you could (most likely) no longer use addr the same way as before, as it would now point to a different (e.g. local) variable.
-Weverything isn't something I would use for my usual builds anyway, because it adds far too much noise, but if you want to keep it, @Kurt Stutsman mentioned a good solution in the comments:

clang++ (g++ doesn't emit a warning in any case) doesn't emit a warning, if you use a reinterpret_cast instead of the c style cast (which you shouldn't use anyway), although both have (in this case) exactly the same functionality. Maybe because reinterpret_cast explicitly tells the compiler: "Trust me, I know, what I'm doing" .


On a side Note: In c++ code you don't need the struct keywords.



回答2:

Well -Weverything enables quite a lot of warnings some of them are known to throw unwanted warnings.

Here your code fires the cast-align warning, that says explicitely

cast from ... to ... increases required alignment from ... to ...

And it is the case here because the alignement for struct addr is only 2 whereas it is 4 for struct addr_in.

But you (and the programmer for getaddrinfo...) know that the pointer p->ai_addr already points to an actual struct addr_in, so the cast is valid.

You can either:

  • let the warning fire and ignore it - after all it is just a warning...
  • silence it with -Wno-cast-align after -Weverything

I must admit that I seldom use -Weverything for that reason, and only use -Wall


Alternatively, if you know that you only use CLang, you can use pragmas to explicetely turn the warning only on those lines:

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

....
}


回答3:

To elaborate on the memcpy version. I thnk this is needed for ARM which cannot have misalligned data.

I created a struct that contains just the first two fields (I only needed port)

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

Then to get the port out, I used memcpy to move the data to the stack

struct sockaddr_in_header   sinh;
unsigned short              sin_port;

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

And return the port

sin_port = ntohs(sinh.sin_port);

This answer is really related to getting the port on Arm

How do I cast sockaddr pointer to sockaddr_in on Arm

The powers that be think that to be the same question as this one, however I dont want to ignore warnings. Experience has taught me that is a bad idea.