Here's struct sockaddr
:
struct sockaddr {
uint8_t sa_len;
sa_family_t sa_family;
char sa_data[14];
};
and, for instance, here's struct sockaddr_in
:
struct sockaddr_in {
uint8_t sa_len;
sa_family_t sa_family;
in_port_t sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
and struct sockaddr_in6
:
struct sockaddr_in6 {
uint8_t sa_len;
sa_family_t sa_family;
in_port_t sin_port;
uint32_t sin6_flowinfo;
struct in6_addr sin6_addr;
};
You'll note that they all share the first two members in common. Thus, functions like bind()
can accept a pointer to a generic struct sockaddr
and know that, regardless of what specific struct
it actually points to, it'll have sa_len
and sa_family
in common (and "in common" here means "laid out the same way in memory", so there won't be any weirdness where both struct
s have an sa_family
member, but they're in totally different places in the two different struct
s. Technically sa_len
is optional, but if it's not there, none of the struct
s will have it, so sa_family
will still be aligned in the same way, and often the datatype of sa_family_t
will be increased to make up the difference in size). So, it can access sa_family
and determine exactly what type of struct
it is, and proceed accordingly, e.g. something like:
int bind(int socket, const struct sockaddr *address, socklen_t address_len) {
if ( address->sa_family == AF_INET ) {
struct sockaddr_in * real_struct = (struct sockaddr_in *)address;
/* Do stuff with IPv4 socket */
}
else if ( address->sa_family == AF_INET6 ) {
struct sockaddr_in6 * real_struct = (struct sockaddr_in6 *)address;
/* Do stuff with IPv6 socket */
}
/* etc */
}
(Pedantic note: technically, according to the C standard [section 6.5.2.3.6 of C11], you're only supposed to inspect common initial parts of struct
s like this if you embed them within a union
, but in practice it'll almost always work without one, and for simplicity I haven't used one in the above code).
It's basically a way of getting polymorphism when you don't actually have real OOP constructs. In other words, it means you don't have to have a bunch of functions like bind_in()
, bind_in6()
, and all the rest of it, one single bind()
function can handle them all because it can figure out what type of struct
you actually have (provided that you set the sa_family
member correctly, of course).
The reason you need the actual cast is because C's type system requires it. You have a generic pointer in void *
, but beyond that everything has to match, so if a function accepts a struct sockaddr *
it just won't let you pass anything else, including a struct sockaddr_in *
. The cast essentially tells the compiler "I know what I'm doing, here, trust me", and it'll relax the rules for you. bind()
could have been written to accept a void *
instead of a struct sockaddr *
and a cast would not have been necessary, but it wasn't written that way, because:
It's semantically more meaningful -
bind()
isn't written to accept any pointer whatsoever, just one to astruct
which is "derived" fromstruct sockaddr
; andThe original sockets API was released in 1983, which was before the ANSI C standard in 1989, and its predecessor - K&R C - just didn't have
void *
, so you were going to have to cast it to something in any case.