Lone Learner Lone Learner - 2 months ago 10
C Question

Is it legal to type-cast pointers of different struct types (e.g. struct sockaddr * to struct sockaddr_in6 *)?

Here is a program that type-casts between pointers of type

struct shape
,
struct rectangle
and
struct triangle
.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

enum { RECTANGLE, TRIANGLE, MAX };

struct shape {
int type;
};

struct rectangle {
int type;
int x;
int y;
};

struct triangle {
int type;
int x;
int y;
int z;
};

struct shape *get_random_shape()
{
int type = rand() % MAX;
if (type == RECTANGLE) {
struct rectangle *r = malloc(sizeof (struct rectangle));
r->type = type;
r->x = rand() % 10 + 1;
r->y = rand() % 10 + 1;
return (struct shape *) r;
} else if (type == TRIANGLE) {
struct triangle *t = malloc(sizeof (struct triangle));
t->type = type;
t->x = rand() % 10 + 1;
t->y = rand() % 10 + 1;
t->z = rand() % 10 + 1;
return (struct shape *) t;
} else {
return NULL;
}
}

int main()
{
srand(time(NULL));

struct shape *s = get_random_shape();

if (s->type == RECTANGLE) {
struct rectangle *r = (struct rectangle *) s;
printf("perimeter of rectangle: %d\n", r->x + r->y);
} else if (s->type == TRIANGLE) {
struct triangle *t = (struct triangle *) s;
printf("perimeter of triangle: %d\n", t->x + t->y + t->z);
} else {
printf("unknown shape\n");
}

return 0;
}


Here is the output.

$ gcc -std=c99 -Wall -Wextra -pedantic main.c
$ ./a.out
perimeter of triangle: 22
$ ./a.out
perimeter of triangle: 24
$ ./a.out
perimeter of rectangle: 8


You can see above that the program compiled and ran without any warnings. I am trying to understand if it is valid to type-cast a pointer of
struct shape
into
struct rectangle
and vice-versa even though both the structs are of different sizes.

If your answer is that this is not valid, then please consider that network programming books routinely typecast between
struct sockaddr *
,
struct sockaddr_in *
and
struct sockaddr_in6 *
pointers depending on the socket family (AF_INET vs. AF_INET6), and then explain why such type cast is okay in case of
struct sockaddr *
but not in the above case of
struct shape *
. Here is an example of type cast with
struct sockaddr *
.

#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

int main()
{
struct addrinfo *ai;

if (getaddrinfo("localhost", "http", NULL, &ai) != 0) {
printf("error\n");
return EXIT_FAILURE;
}

if (ai->ai_family == AF_INET) {
struct sockaddr_in *addr = (struct sockaddr_in *) ai->ai_addr;
printf("IPv4 port: %d\n", addr->sin_port);
} else if (ai->ai_family == AF_INET6) {
struct sockaddr_in6 *addr = (struct sockaddr_in6 *) ai->ai_addr;
printf("IPv6 port: %d\n", addr->sin6_port);
}

return 0;
}


This code compiles and runs fine as well. Moreover, this is the recommended way of writing such programs as per books on socket programming.

$ gcc -std=c99 -D_POSIX_SOURCE -Wall -Wextra -pedantic foo.c
$ ./a.out
IPv6 port: 20480

Answer

Is it legal to type-cast pointers of different struct types (e.g. struct sockaddr * to struct sockaddr_in6 *)?

Yes. C explicitly provides for it:

A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined. Otherwise, when converted back again, the result shall compare equal to the original pointer.

(C2011, 6.3.2.3/7)

As other answers have pointed out, it is not the cast itself that is the problem, but what you do with the result. And that comes down to the Strict Aliasing Rule:

An object shall have its stored value accessed only by an lvalue expression that has one of the following types:

  • a type compatible with the effective type of the object,

[... plus several other alternatives that cannot apply in this case ...]

(C2011, 6.5/7; emphasis added)

The main question, therefore, is what is the effective type of the object to which the struct sockaddr * points? It's important here to understand that we can't tell from the declaration of getaddrinfo(), nor that of struct addrinfo. In particular, there is no reason to assume that the effective type is struct sockaddr.

In fact, given that the cast you've asked about is the standard and intended method for accessing the address details, there is every reason to suppose that getaddrinfo() supports that by ensuring that the effective type is the one indicated by the associated ai_family code. Then the corresponding cast yields a pointer matching the effective type of the address info. In that case, there is no problem inherent in accessing the address info via the pointer obtained via the cast.

I observe in support of the above that it is reasonable to suppose that the pointer in question points to a dynamically allocated object. The effective type of such an object depends on the means by which its stored value was last set (C2011, 6.5/6). It is not only plausible but likely that getaddrinfo() would set that value in a manner that gives it the wanted effective type. For example, code along the same lines as your shape example would do so.

Ultimately, casting the struct sockaddr * to and from pointers to the address-family-specific structs is the intended use, and there is no reason to suppose that an environment that provides getaddrinfo() would, in practice, allow those behaviors to be dubious. If it had been necessary, POSIX (by whom the function is specified) could have incorporated a special rule allowing the casts. But no such rule is needed in this case, although POSIX makes you take that on faith.