繁体   English   中英

类型转换不同结构类型的指针是否合法(例如struct sockaddr * to struct sockaddr_in6 *)?

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

这是一个在struct shapestruct rectanglestruct 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;
}

这是输出。

$ 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与AF_INET6),然后解释为什么这样的类型在struct sockaddr *情况下,cast是可以的,但在上面的struct shape *情况下则没有。 以下是使用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;
}

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

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

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

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

或来自

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

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

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

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

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

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

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

问题是,如果你犯了错误,比如

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访问不存在的成员。

对不同结构类型的类型转换指针是否合法(例如struct sockaddr * to struct sockaddr_in6 *)?

是。 C明确规定:

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

(C2011,6.3.2.3/7)

正如其他答案所指出的那样,不是演员本身就是问题,而是你对结果做了什么。 这归结为严格的别名规则:

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

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

[...加上其他一些在这种情况下无法适用的替代方案......]

(C2011,6.5 / 7;重点补充)

因此,主要问题是struct sockaddr *指向的对象的有效类型是什么? 重要的是要理解我们不能从getaddrinfo()的声明,也不能告诉struct addrinfo 特别是,没有理由假设有效类型是struct sockaddr

事实上,鉴于您所询问的演员是访问地址详细信息的标准和预期方法,因此有充分理由认为getaddrinfo()通过确保有效类型是由关联的ai_family指示的类型来支持它。码。 然后相应的强制转换产生一个与地址信息的有效类型相匹配的指针。 在这种情况下,通过强制转换获得的指针访问地址信息没有固有的问题。

我观察到,为了支持上述内容,可以合理地假设所讨论的指针指向动态分配的对象。 此类对象的有效类型取决于其存储值的最后设置方式(C2011,6.5 / 6)。 这不仅是合理的,而且getaddrinfo()可能会以一种给出所需有效类型的方式设置该值。 例如,与形状示例相同的代码将执行此操作。

最终,将struct sockaddr *转换为指向地址族特定结构的指针是预期用途,并且没有理由认为提供getaddrinfo()的环境在实践中会允许这些行为是可疑的。 如果有必要,POSIX(指定函数)可以包含允许演员表的特殊规则。 但是在这种情况下不需要这样的规则,尽管POSIX让你在信仰上采取这种规则。

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

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

当一个指针指向一个sockaddr_storage结构被强制转换为指针sockaddr结构中, ss_family所述的场sockaddr_storage结构应映射到sa_family所述的字段sockaddr结构。 当指向sockaddr_storage结构的指针被转换为指向特定于协议的地址结构的指针时, ss_family字段应映射到该结构的字段sa_family_t并标识协议的地址族。

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

给定的编译器可能需要魔术来实现它,但是这个库特别需要为您完成。

你的问题有几个术语混淆。

首先,只是因为你的程序以某种方式“编译并运行而没有任何警告”甚至产生了你期望的结果,它仍然不意味着你在代码中所做的事情在某种程度上是“有效的”。

其次,你似乎在询问演员本身的有效性。 实际上,演员本身就是重点。 在C中有很多东西你可以互相“强加”。 但是,该语言无法保证您可以对此类强制转换的结果执行哪些操作。 演员本身可能完全有效,但您对结果采取的进一步行动可能非常无效。

第三,这显然是你的问题的真正含义:在指向不同结构类型的指针之间进行投射,这些结构类型共享一个共同的初始子序列,然后通过结果指针从该公共子序列访问成员。 这不是演员问题,而是后续访问 答案是:不,语言没有将此定义为有效的技术。 该语言允许您检查在公共联合中联合的不同结构类型的公共初始子序列,但是如果没有公共联合,则不允许这样做。

至于在struct sockaddr *struct sockaddr_in *struct sockaddr_in6 *之间使用强制转换的流行技术 - 这些只是与C语言无关的黑客攻击。 它们只是在实践中工作,但就C语言而言,该技术无效。

它实际上不能保证工作。 如果编译器看到具有三种类型的union的声明, 保证可以工作; 编译器看到声明就足够了。 在那种情况下,访问结构的公共前导元素的代码很好。 显然,最重要的共同元素是“类型”成员。

所以,如果你已经声明结构形状,长方形和三角形的工会,可以采取一个指针,它确实点到三个结构之一,转换指针,访问类型字段,然后从那里。

但这不适用于任何语言。 同样在C ++中,您应该在基类中包含所有变量,并在基类中声明虚函数。 而不是移动到形状而不是矩形,更好地移动到void *而不是矩形然后这是一个面向对象的范例。 Hinerhitance,polimorphimsum和其他正是将语言定位于对象的方式。 要在C中使用对象,您应该硬编码。 但很值得。 我认为程序的平均复杂性并不适合转向C ++。 法拉利和卡车之间存在差异。 至少你不必为此工作,C很有趣。 在你的地方,我会这样做:

typedef enum shape_type{
circle,
rectangle,
triangle,
//...
}S_type;

typedef struct shape
{
   S_type stype;
   int ar_par[4];//default allocated parameters number
   int* p_par; //to default it is going to contain the ar_par address
               //and you are going to change it case you needs more  parameters. You save a malloc more
   int n;//count of parameters
   int (*get_perimeter) (struct shape *);//you can also typedef them
   int (*get_area)(struct shape*);
}*Shape_ptr,Shape;

而不是编码这样的

Shape_ptr new_rectangle(int a, int b)
{
   Shape_ptr res=malloc(sizeof(Shape));
   res->stype=rectangle;
   res->p_par=res->ar_par;//no need to allocate anything
   *res->p_par[0]=a;*res->p_par[1]=b;
   res->n=2;
   res->get_perimeter=get_rectangle_perimeter;
   res->get_area=get_rectangle_area;

}
int get_rectangle_perimeter(Shape_ptr s)
{
   return s->p_par[0]<<1 + s->p_par[1]<<1; //or multiply by two;
}
main() 
{
    Shape_ptr shp =get_random_shape() ; //this function is going to call     new_rectangle
    printf ("shap area is:%d\n",(*shp->get_area)(shp);
}

等等......这就是你如何处理C中的对象。面向对象的程序,包含一些范例,在大型程序中简化了程序员的生活

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM