Click here to Skip to main content
15,889,528 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
Will casting around sockaddr_storage and sockaddr_in break strict aliasing? For example in the code below, I'm filling sockaddr_storage structure with a pointer of type `sockaddr_in` and then passing it to a function that accepts a pointer of type `sockaddr` -

C++
sockaddr_storage addrStruct;
sockaddr_in * tmp = reinterpret_cast<struct sockaddr_in *> (&addrStruct);
tmp->sin_family = AF_INET;
tmp->sin_port = htons(port);
inet_pton(AF_INET, addr, tmp->sin_addr);

// int socket = socket(...);
// int error  = bind(socket, (sockaddr *)&addrStruct, sizeof(addrStruct));
// ...


Here's what some official documentation say -

Microsoft's documentation -

Quote:
Application developers normally use only the ss_family member of the SOCKADDR_STORAGE. The remaining members ensure that the SOCKADDR_STORAGE can contain either an IPv6 or IPv4 address and the structure is padded appropriately to achieve 64-bit alignment. Such alignment enables protocol-specific socket address data structures to access fields within a SOCKADDR_STORAGE structure without alignment problems. With its padding, the SOCKADDR_STORAGE structure is 128 bytes in length.


Opengroup's documentation -

Quote:
The <sys/socket.h> header shall define the sockaddr_storage structure. This structure shall be:

- Large enough to accommodate all supported protocol-specific address structures

- Aligned at an appropriate boundary so that pointers to it can be cast as pointers to protocol-specific address structures and used to access the fields of those structures without alignment problems


Man page of socket -

Quote:
In addition, the sockets API provides the data type struct sockaddr_storage. This type is suitable to accommodate all supported domain-specific socket address structures; it is large enough and is aligned properly. (In particular, it is large enough to hold IPv6 socket addresses.)


You can also see previous discussion at SO threads.

What I have tried:

There are two other methods that were recommended to me - using a union of all the structs and then putting and retrieving values to/back from that union only. But it is also said to be undefined behaviour under C99 standard. Next one looks like this -

struct sockaddr_in sin;

sin.sin_family = AF_INET;
sin.sin_port= htons(port);


First we create the required structure to be filled in sockaddr_storage and fill it with required values. Then we memcpy it to sockaddr_storage structure -

memcpy(reinterpret_cast<char *>(&addrStruct),
       reinterpret_cast<char *>(&sin), sizeof(sin));
Posted
Updated 15-Feb-17 1:03am

1 solution

There has been already much said in the mentioned SO thread. But I would not care too much. Here is my (a bit pragmatic) view:

These socket structures are a little bit special because the sockaddr parameter of the socket functions is usually never passed as such but by casting an appropriate type like sockaddr_in or sockaddr_in6. The first member of the structures (the family) defines how the passed data will be interpreted (and therefore also the max. size of the passed struct).

So casting here is by intention (don't forget that the socket functions are from the C standard library).

It is common to declare a variable of the required type and pass that casting it to sockaddr. So there is no need to create a larger struct when you only want to pass an IPv4 struct for example. If you would have to decide the data type during program execution you can use a larger struct like sockaddr_storage and cast that like you have done or use two code blocks with the matching structs and call the socket function within the block.

In any case there should be no need to copy the data.

BTW: There is no need to cast here:
memcpy(reinterpret_cast<char *>(&addrStruct),
       reinterpret_cast<char *>(&sin), sizeof(sin));
The memcpy parameters are void* and not char*.
 
Share this answer
 
Comments
Member 13002551 15-Feb-17 8:07am    
The sole existence of sockaddr_storage is in case you want to make domain unaware functions eg same function send() for ipv4, ipv6, sockaddr_un and alike, which is what I'm doing. Are you saying the code containing casts that I provided doesn't break strict aliasing rule? The char* cast is because char is the only type that aliases with any other type.
Jochen Arndt 15-Feb-17 9:07am    
No, I don't said yes. My point is that you have to cast anyway when calling bind(). You are calling a C function passing a C structure. You are not in the C++ space.

If you don't like that (and the warning), you may create a union and maybe a C++ class hiding the C parts. But that does not solve any problems (besides the warning). If your data are not initialised properly or buffers are too small your code will always crash.

In other words:
Your usage of memcpy is just hiding the problem by using void* pointers.

"The char* cast is because char is the only type that aliases with any other type."
But the argument is void* so you should have casted to that. But that is not necessary because any pointer can be assigned to void* without casting.

BTW:
I have not checked it but is the warning also shown when using C style casting as used with the bind call?
Member 13002551 15-Feb-17 8:12am    
Compiler disagrees - x86-64 gcc 7 (snapshot)!!warning: dereferencing type-punned pointer might break strict-aliasing rules [-Wstrict-aliasing]
Jochen Arndt 15-Feb-17 9:29am    
Where?

I have just compiled all your code snippets and don't have that warning (using the x86-32 compiler with an additional code line that triggers the warning for testing).
Member 13002551 15-Feb-17 9:49am    
my code has commented lines to bind() call, but if you uncomment them it will show them. otherwise another example here - https://godbolt.org/g/SMCdSk

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900