4

I came across a socket programming tutorial in which it is quoted

"a pointer to a struct sockaddr_in can be cast to a pointer to a struct sockaddr and vice-versa"

I dont understand how can sockaddr_in be cast to sockaddr. Casting a pointer of Big type to Small type should give UD behavior.

struct sockaddr {
unsigned short sa_family; // address family, AF_xxx
char sa_data[14]; // 14 bytes of protocol address
};


struct sockaddr_in {
short int sin_family; // Address family, AF_INET
unsigned short int sin_port; // Port number
struct in_addr sin_addr; // Internet address
unsigned char sin_zero[8]; // Same size as struct sockaddr
};

How can the cast not be undefined? Isn't it unsafe to cast these to each other?

If i have a class A having only two ints and class B having 4 ints. And if i have a pointer of type B and i cast it to type A then sure i can fetch the first two elements. But if class A has 2 chars declared first and 2 ints declared later then the pointers would not right fetch the values since the object layout in this case would be different.

Edit 1:

class Anu
{
public:
    char a;
    int b;
    Anu()
    {
        a='a';
    }
};
class Anurag
{
public:
    Anurag() { a=4;}
    int a;
    int b;
    int c;
    int d;
};
int main()
{
        Anu objanu;
        Anurag objanurag;
        Anurag *ptrAnurag= &objanurag;
        ptrAnurag= (Anurag*)&objanu;
        cout<<ptrAnurag->a; //Some weird value here
        return 0;
}

Assuming i change the example so that both classes have same size by adjusting the variables types...still the object layout might be different even though the size remains the same.

davmac
  • 20,150
  • 1
  • 40
  • 68
anurag86
  • 1,635
  • 1
  • 16
  • 31
  • 1
    How are they different size? sockaddr: 2+14=16, sockaddr_in:2+2+4+8=16 – Amer Agovic Oct 24 '15 at 14:39
  • Yes you are right. But the object layout of sockaddr and sockaddr_in would be different even though the size appears to be similar. The first thing that would be stored in sockaddr would be 2 bytes representing sa_family. However in sockaddr_in object the first thing stored would be an int of 4 bytes representing sin_family. The result would be UD if sockaddr_in type pointer points to sockaddr or viceversa. – anurag86 Oct 24 '15 at 14:49
  • no no but sin_family is not 'int' it is 'short int' thank the c++ guys for that but I use just 'short' to short that issues :) – Amer Agovic Oct 24 '15 at 14:58
  • what about sin_port vs sa_data types? – anurag86 Oct 24 '15 at 15:10

3 Answers3

6

I'll add to @gsamaras answer by saying that Undefined Behaviour doesn't always means that bad things are about to happen. Undefined Behaviour actually says "we* don't provide any specifications on what should happen next if XYZ occurs".

(*the C++ standard).

this is the place where the OS takes place and say "it is defined by us".

although casting unrelated structs (sockaddr_in,sockaddr) may be undefined behaviour by the standard, the OS API specify that it is valid with their API.

David Haim
  • 25,446
  • 3
  • 44
  • 78
1

The different sizes don't matter. Just like you can pass strings of different lengths to the various string-handling functions, you can pass struct sockaddr of different lengths to the various socket-handling functions.

The size of the struct sockaddr is interpreted by the called function per the contents of the sa_family member of the structure. Note also that all functions that take a struct sockaddr * address also take a socklen_t argument that holds the size of the structure being passed.

For example, the struct sockaddr_un structure is 110 bytes:

   struct sockaddr_un {
       sa_family_t sun_family;               /* AF_UNIX */
       char        sun_path[108];            /* pathname */
   };

The called function such as bind() or getpeername() have declarations similar to

int getpeerame(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 

for the very reason that the size(s) of various socket structures vary.

Note that the first member of every struct sockaddr_??? is the sa_family. Thus it's always in the same place.

Andrew Henle
  • 32,625
  • 3
  • 24
  • 56
0

They are of equal size, so no, you don't get any UB!

Proof:

#include <stdio.h>

struct sockaddr {
  unsigned short sa_family; // address family, AF_xxx
  char sa_data[14]; // 14 bytes of protocol address
};

// src: http://www.gta.ufrj.br/ensino/eel878/sockets/sockaddr_inman.html
struct in_addr {
    unsigned long s_addr;  // load with inet_aton()
};

struct sockaddr_in {
  short int sin_family; // Address family, AF_INET
  unsigned short int sin_port; // Port number
  struct in_addr sin_addr; // Internet address
  unsigned char sin_zero[8]; // Same size as struct sockaddr
};

int main (void) {
  printf("%zu\n", sizeof(unsigned short) + sizeof(char) * 14);
  printf("%zu\n", sizeof(short int) + sizeof(unsigned short int) + sizeof(struct in_addr) + sizeof(unsigned char) * 8);
  return 0;
}

Output:

16
16

A good comment: sockaddr: 2+14=16, sockaddr_in:2+2+4+8=16 – Amer Agovic

You may also want to take a look at this question: Why isn't sizeof for a struct equal to the sum of sizeof of each member?


Please, check this question: Is it possible to cast struct to another?

I am copying the answer of Benoit here too:

This is refered as Type Punning. Here, both structures have the same size, so there is no question of struct size. Although you can cast almost anything to anything, doing it with structures is error-prone.

and this one by Richard J. Ross III too:

This is C's form of "inheritance" (notice the quotes). This works because C does not care about the underlying data in an address, just what you represent it as.

The function determines what structure it actually is by using the sa_family field, and casting it into the proper sockaddr_in inside the function.

Community
  • 1
  • 1
gsamaras
  • 71,951
  • 46
  • 188
  • 305
  • Yes you are right. But the object layout of sockaddr and sockaddr_in would be different even though the size appears to be similar. The first thing that would be stored in sockaddr would be 2 bytes representing sa_family. However in sockaddr_in object the first thing stored would be an int of 4 bytes representing sin_family. The result would be UD if sockaddr_in type pointer points to sockaddr or viceversa. – anurag86 Oct 24 '15 at 14:50
  • 1
    The size of the base structure that has it's address cast to `struct sockaddr *` doesn't matter - see `struct sockaddr_un`, which is 110 bytes. Every function that takes a `struct sockaddr *` also takes a `socklen_t` for this reason. See http://man7.org/linux/man-pages/man7/unix.7.html – Andrew Henle Oct 24 '15 at 15:03
  • @gsamaras: Thnx for the answer. Could you please look at my Edit1 part of the question. Things are still little unclear to me. – anurag86 Oct 24 '15 at 15:12
  • You are welcome @anurag86. I think in your example, the classes are not of equal size! – gsamaras Oct 24 '15 at 15:17
  • 1
    @gsamaras: if i do getaddinfo this way : `getaddrinfo(myhost, myport, &hints, &res);` , i get the result in _res_ and then i can use the c style cast this way : `struct sockaddr_in *mytest = (struct sockaddr_in *)res->ai_addr;` . It works fine. But if i use c++ style `static_cast` then it doesnt work : `struct sockaddr_in *mytest = static_cast(res->ai_addr);` . I am not sure what type of cast is being used internally by C. But it is evident from the example that it is not `static_cast` . Shouldn't the `static_cast` work? – anurag86 Oct 26 '15 at 06:32
  • I would let it in the C way @anurag86, since structs are C elements. – gsamaras Oct 26 '15 at 15:50
  • 2
    The fact that they have "the same size" (or not) has absolutely no relevance whatsoever in this case. C does not help here either: the behavior is undefined in C as much as it is undefined in C++. – AnT stands with Russia Feb 11 '18 at 15:45