首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >键入不同结构类型的指针(例如sockaddr *到struct sockaddr_in6 *)是否合法?

键入不同结构类型的指针(例如sockaddr *到struct sockaddr_in6 *)是否合法?
EN

Stack Overflow用户
提问于 2016-09-10 11:07:19
回答 6查看 3.5K关注 0票数 4

下面是一个在struct shapestruct rectanglestruct triangle类型的指针之间进行类型转换的程序.

代码语言:javascript
运行
复制
#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;
}

这是输出。

代码语言:javascript
运行
复制
$ 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

您可以在上面看到,程序编译并运行时没有任何警告。我试图理解将struct shape的指针类型转换为struct rectangle是否有效,反之亦然,尽管这两个结构都有不同的大小。

如果您的回答是这是无效的,那么请考虑网络编程书籍通常会根据套接字系列在struct sockaddr *struct sockaddr_in *struct sockaddr_in6 *指针之间键入类型(AF_INET vs. AF_INET6),然后解释为什么在struct sockaddr *的情况下,这种类型的类型转换是可以的,而在上面的struct shape *情况下则不行。下面是一个使用struct sockaddr *进行类型转换的示例。

代码语言:javascript
运行
复制
#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;
}

这段代码也编译和运行良好。此外,这是按照套接字编程的书籍编写此类程序的推荐方法。

代码语言:javascript
运行
复制
$ gcc -std=c99 -D_POSIX_SOURCE -Wall -Wextra -pedantic foo.c
$ ./a.out 
IPv6 port: 20480
EN

回答 6

Stack Overflow用户

回答已采纳

发布于 2016-09-11 05:41:13

键入不同结构类型的指针(例如sockaddr *到struct sockaddr_in6 *)是否合法?

是。C明确规定:

指向对象类型的指针可以转换为指向不同对象类型的指针。如果结果指针未针对引用类型正确对齐,则行为未定义。否则,当再次转换回时,结果将与原始指针进行比较。

(C2011,6.3.2.3/7)

正如其他答案所指出的,问题不在于演员本身,而在于你如何处理结果。这可以归结为严格的别名规则:

对象的存储值只能由具有下列类型之一的lvalue表达式访问:

  • 与对象的有效类型兼容的类型,

..。再加上其他几种不适用于这种情况的选择.

(C2011,6.5/7;重点添加)

因此,主要的问题是,struct sockaddr *指向的对象的有效类型是什么?在这里重要的是要明白,我们不能从getaddrinfo()__,的声明和 struct addrinfo.的声明中看出特别是,没有理由假设有效类型是struct sockaddr

事实上,考虑到您所询问的强制转换是访问地址详细信息的标准和预定方法,我们完全有理由假设getaddrinfo()通过确保有效类型是由关联的ai_family代码所指示的类型来支持这种方法。然后,对应的强制转换产生一个指针,该指针与地址信息的有效类型相匹配。在这种情况下,通过通过转换获得的指针访问地址信息没有固有的问题。

为了支持上面的内容,我发现,合理的假设是,所讨论的指针指向动态分配的对象。这类对象的有效类型取决于最后设置其存储值的方法(C2011,6.5/6)。这不仅是合理的,而且很可能getaddrinfo()会以一种赋予它想要的有效类型的方式来设置这个值。例如,与形状示例相同的代码就是这样做的。

最终,将struct sockaddr *转换为指向特定于地址家族的结构的指针是预期的用途,没有理由认为提供getaddrinfo()的环境在实践中会允许这些行为产生怀疑。如果有必要的话,POSIX (由谁指定该功能)可以包含一条允许转换的特殊规则。但是在这种情况下不需要这样的规则,尽管POSIX让你以信仰来看待它。

票数 3
EN

Stack Overflow用户

发布于 2016-09-10 11:51:33

如果移除显式类型转换,编译器将忠实地诊断错误。

代码语言:javascript
运行
复制
struct rectangle *r = (struct rectangle *) s;

或来自

代码语言:javascript
运行
复制
struct triangle *t = (struct triangle *) s;

在这种情况下,显式类型转换被允许工作,因为标准所要求的是什么。实际上,通过在这两个语句中使用显式类型转换,可以有效地指导编译器“闭嘴,我知道我在做什么”。

更有趣的是,为什么main()函数在运行时工作,一旦您将编译器大棒提交,它就允许转换了。

代码之所以有效,是因为所有三个struct的第一个成员都是相同的类型。struct的地址等于其第一个成员的地址,但类型不同(即指向struct rectangle的指针具有与指向int的指针不同的类型)。因此(如果忽略不同的类型),测试s == &(s->type)将为真。类型转换的使用处理这个问题,所以(int *)s == &s->type

一旦您的代码完成了该测试,它就会在s上执行显式类型转换。碰巧,在声明中

代码语言:javascript
运行
复制
struct rectangle *r = (struct rectangle *) s;

您的代码确保s实际上是(动态分配的) struct rectangle的地址。因此,r的后续使用是有效的。类似地,在else if块中使用struct triangle

问题是,如果您犯了一个错误,例如

代码语言:javascript
运行
复制
if (s->type == RECTANGLE)
{
    struct triangle *t = (struct triangle *) s;
    printf("perimeter of triangle: %d\n", t->x + t->y + t->z);
}

(也就是说,像使用struct rectangle一样使用struct triangle),那么编译器仍将忠实地允许类型转换(如上面所讨论的)。但是,由于s实际上不是struct triangle的地址,所以行为现在还没有定义。特别地,访问t->z访问一个不存在的成员.

票数 3
EN

Stack Overflow用户

发布于 2016-09-11 05:51:02

在伯克利套接字库的特定情况下,POSIX标准保证您可以将指向指向任何类型套接字的指针的指针转换为struct sockaddr_storage,并且标识套接字类型的字段将正确映射。

具体来说,POSIX标准指定了struct sockaddr_storage

当指向sockaddr_storage结构的指针被转换为指向sockaddr结构的指针时,sockaddr_storage结构的ss_family字段将映射到sockaddr结构的sa_family字段。当指向sockaddr_storage结构的指针转换为指向特定于协议的地址结构的指针时,ss_family字段将映射到该结构的字段上,该字段的类型为sa_family_t并标识该协议的地址族。

它还说 of struct sockaddr_in,“这种类型的指针将由应用程序转换到struct sockaddr *,以便与套接字函数一起使用。bind()connect()等的接口只有在库查找它获得的const struct sockaddr*并确定它指向的套接字类型时才能工作。

给定的编译器可能需要魔法才能实现这一点,但是这个库尤其需要为您完成。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/39425409

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档