Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
918 views
in Technique[技术] by (71.8m points)

c - 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
", r->x + r->y);
    } else if (s->type == TRIANGLE) {
        struct triangle *t = (struct triangle *) s;
        printf("perimeter of triangle: %d
", t->x + t->y + t->z);
    } else {
        printf("unknown shape
");
    }

    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
");
        return EXIT_FAILURE;
    }

    if (ai->ai_family == AF_INET) {
        struct sockaddr_in *addr = (struct sockaddr_in *) ai->ai_addr;
        printf("IPv4 port: %d
", addr->sin_port);
    } else if (ai->ai_family == AF_INET6) {
        struct sockaddr_in6 *addr = (struct sockaddr_in6 *) ai->ai_addr;
        printf("IPv6 port: %d
", 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
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The compiler would faithfully diagnose an error if the explicit type conversions were removed from

struct rectangle *r = (struct rectangle *) s;

or from

struct triangle *t = (struct triangle *) s;

The explicit type conversions, in this case, are permitted to work because what is what the standard requires. In effect, by using the explicit type conversion in these two statements you are effectively directing the compiler "shut up, I know what I'm doing".

What is more interesting is why the main() function works at run time, once you have bludgeoned the compiler into submission so it permits the conversion.

The code works because the first member of all three structs are the same type. The address of a struct is equal to the address of its first member, except that the types are different (i.e. a pointer to a struct rectangle has different type from a pointer to an int). Therefore (if we ignore the different types), the test s == &(s->type) will be true. The use of a type conversion deals with that, so (int *)s == &s->type.

Once your code has completed that test, it is then doing an explicit type conversion on s. It happens that, in the declaration

struct rectangle *r = (struct rectangle *) s;

that your code has ensured s is actually the address of a (dynamically allocated) struct rectangle. Hence the subsequent usage of r is valid. Similarly in the else if block, with a struct triangle.

The thing is, if you made an error, such as

if (s->type == RECTANGLE)
{
    struct triangle *t = (struct triangle *) s;
    printf("perimeter of triangle: %d
", t->x + t->y + t->z);
}

(i.e. using a struct rectangle as if it is a struct triangle) then the compiler will still faithfully permit the type conversion (as discussed above). However, the behaviour is now undefined since s is not actually the address of a struct triangle. In particular, accessing t->z accesses a non-existent member.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...