On Mon, Feb 06, 2023 at 02:02:23PM +0800, Xi Ruoyao wrote: > On Sun, 2023-02-05 at 16:31 +0100, Alejandro Colomar via Libc-alpha wrote: > > > The only correct way to use different types in an API is > > through a union. > > I don't think this statement is true (in general). Technically we can > write something like this: > > struct sockaddr { ... }; > struct sockaddr_in { ... }; > struct sockaddr_in6 { ... }; > > int bind(int fd, const struct sockaddr *addr, socklen_t addrlen) > { > if (addrlen < sizeof(struct sockaddr) { > errno = EINVAL; > return -1; > } > > /* cannot use "addr->sa_family" directly: it will be an UB */ > sa_family_t sa_family; > memcpy(&sa_family, addr, sizeof(sa_family)); > > switch (sa_family) { > case AF_INET: > return _do_bind_in(fd, (struct sockaddr_in *)addr, addrlen); > case AF_INET6: > return _do_bind_in6(fd, (struct sockaddr_in6 *)addr, addrlen); > /* more cases follow here */ > default: > errno = EINVAL; > return -1; > } > } > } > > In this way we can use sockaddr_{in,in6,...} for bind() safely, as long > as we can distinguish the "real" type of addr using the leading byte > sequence (and the caller uses it carefully). > > But obviously sockaddr_storage can't be distinguished here, so casting a > struct sockaddr_stroage * to struct sockaddr * and passing it to bind() > will still be wrong (unless we make sockaddr_storage an union or add > [[gnu::may_alias]]). If you wanted to make this work, you can just memcpy sockaddr_storage to a local object of the right declared type to access it. But this is only relevant for a userspace implementation of bind() rather than one that just marshalls it across some syscall boundary to a kernel. Rich