views:

1156

answers:

3

I'm using Beej's Guide to Networking and came across an aliasing issue. He proposes a function to return either the IPv4 or IPv6 address of a particular struct:

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  }

This causes GCC to spit out a strict-aliasing error for sa on line 3. As I understand it, it is because I call this function like so:

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

I'm guessing the aliasing has to do with the fact that the their_addr variable is of type sockaddr_storage and another pointer of a differing type points to the same memory.

Is the best way to get around this sticking sockaddr_storage, sockaddr_in, and sockaddr_in6 into a union? It seems like this should be well worn territory in networking, I just can't find any good examples with best practices.

Also, if anyone can explain exactly where the aliasing issue takes place, I'd much appreciate it.

+1  A: 

... I just can't find any good examples with best practices.

Maybe look here:

http://my-sample-code.googlecode.com/svn/trunk/socket/

Cheers,

lupo

+2  A: 

I tend to do this to get GCC do the right thing with type-punning, which is explicitly allowed 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;

Nikolai N Fetissov
This makes sense, but for some reason I got the impression that type-punning was a no-no. I suppose in some circumstances there isn't a great way around it.
sinoth
union was invented for exactly this - explicit type punning - so compilers have to recognize and handle it.
Nikolai N Fetissov
Union may have been invented for this, and I agree that compilers should handle it. But the standard does not specify it. Support for it is an additional guarantee provided by gcc, so it could fail on another compiler and the compiler's developers would argue that they are in the right. And in the future, gcc developers could do the same thing. In the C world, the trend is to break existing programming practices to gain 0.5% in speed benchmarks, and strict aliasing itself is only one instance of this trend.
Pascal Cuoq
The C99 standard specifically allows unions for this purpose. See http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf §6.5, paragraph 7.
Adam Goode
A: 

The issue has nothing to do with the call to the function. Rather, it's with ((struct sockaddr_in*)sa)->sin_addr. The problem is that sa is a pointer of one type, but you're casting it to a pointer of a different type and then dereferencing it. This breaks a rule called "strict aliasing", which says that variables of different types can never alias. In your case, aliasing to a different type is exactly what you want to do.

The simple solution is to turn off this optimization, which allows aliasing in this manner. On GCC, the flag is -fno-strict-aliasing.

The better solution is to use a union, as mentioned by 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);
}

That said, I can't actually get GCC to give me a warning when using your original code, so I'm not sure if this buys you anything.

Mark Lodato