POSIX intends pointers to variations of struct sockaddr
to be castable, however depending on the interpretation of the C standard this may be a violation of the strict aliasing rule and therefore UB. (See this answer with comments below it.) I can, at least, confirm that there may at least be a problem with gcc: this code prints Bug!
with optimization enabled, and Yay!
with optimization disabled:
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
sa_family_t test(struct sockaddr *a, struct sockaddr_in *b)
{
a->sa_family = AF_UNSPEC;
b->sin_family = AF_INET;
return a->sa_family; // AF_INET please!
}
int main(void)
{
struct sockaddr addr;
sa_family_t x = test(&addr, (struct sockaddr_in*)&addr);
if(x == AF_INET)
printf("Yay!\n");
else if(x == AF_UNSPEC)
printf("Bug!\n");
return 0;
}
Observe this behavior on an online IDE.
To workaround this problem this answer proposes the use of type punning with unions:
/*! 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;
However, apparently things are still not as simple as they look… Quoting this comment by @zwol:
That can work but takes a fair bit of care. More than I can fit into this comment box.
What kind of fair bit of care does it take? What are the pitfalls of the use of type punning with unions to cast between variations of struct sockaddr
?
I prefer to ask than to run into UB.
Using a
union
like this is safe,from C11 §6.5.2.3:
and
(highlighted what I think is most important)
With accessing the
struct sockaddr
member, you will read from the common initial part.Note: This will not make it safe to pass pointers to the members around anywhere and expect the compiler knows they refer to the same stored object. So the literal version of your example code might still break because in your
test()
theunion
is not known.Example:
Here,
test()
might break, buttest2()
will be correct.Given the
address_t
union you proposeand a variable declared as
address_t
,you can safely initialize
addr.sa.sa_family
and then readaddr.sa_in.sin_family
(or any other pair of aliased_family
fields). You can also safely useaddr
in a call torecvfrom
,recvmsg
,accept
, or any other socket primitive that takes astruct sockaddr *
out-parameter, e.g.And you can also go in the other direction,
It is also okay to allocate
address_t
buffers withmalloc
, or embed it in a larger structure.What's not safe is to pass pointers to individual sub-structures of an
address_t
union to functions that you write. For instance, yourtest
function ...... may not be called with
(void *)a
equal to(void *)b
, even if this happens because the callsite passed&addr.sa
and&addr.sa_in
as the arguments. Some people used to argue that this should be allowed when a complete declaration ofaddress_t
was in scope whentest
was defined, but that's too much like "spukhafte Fernwirkung" for the compiler devs; the interpretation of the "common initial subsequence" rule (quoted in Felix's answer) adopted by the current generation of compilers is that it only applies when the union type is statically and locally involved in a particular access. You must write insteadYou might be wondering why it's okay to pass
&addr.sa
toconnect
then. Very roughly,connect
has its own internaladdress_t
union, and it begins with something likeat which point it can safely inspect
xaddr.sa.sa_family
and thenxaddr.sa_in.sin_addr
or whatever.Whether it would be okay for
connect
to just cast itsaddr
argument toaddress_t *
, when the caller might not have used such a union itself, is unclear to me; I can imagine arguments both ways from the text of the standard (which is ambiguous on certain key points having to do with the exact meanings of the words "object", "access", and "effective type"), and I don't know what compilers would actually do. In practiceconnect
has to do a copy anyway, because it's a system call and almost all memory blocks passed across the user/kernel boundary have to be copied.