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

时间:2016-09-10 11:07:19

标签: c pointers struct

这是一个在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 vs. AF_INET6),然后解释为什么在struct sockaddr *的情况下这种类型转换是可以的,但在上述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

6 个答案:

答案 0 :(得分:3)

如果从

中删除了显式类型转换,编译器会忠实地诊断出错误
struct rectangle *r = (struct rectangle *) s;

或来自

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

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

更有趣的是,为什么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会访问不存在的成员。

答案 1 :(得分:3)

  

对不同结构类型的类型转换指针是否合法(例如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让你凭信心承担这一点。

答案 2 :(得分:2)

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

具体而言,the POSIX standard指定struct sockaddr_storage

  

当指向sockaddr_storage结构的指针被强制转换为指针时   一个sockaddr结构,ss_family的{​​{1}}字段   结构应映射到sockaddr_storage的{​​{1}}字段   结构体。当指向sa_family结构的指针被强制转换为   指向协议特定地址结构的指针,sockaddr字段   应映射到sockaddr_storage类型的结构的字段   并且标识协议的地址族。

ss_family的{​​p> It also says,“此类型的指针应由应用程序强制转换为sa_family_t以用于套接字函数。”struct sockaddr_in的接口,{{1只有当库查找它获得的struct sockaddr *并找出它所指向的套接字类型时,才能使用等等。

给定的编译器可能需要魔术来实现它,但是这个库尤其需要为你做。

答案 3 :(得分:1)

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

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

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

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

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

答案 4 :(得分:0)

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

所以如果你声明了一个结构形状,矩形和三角形的联合,你可以拿一个指向三个结构之一的指针,投射指针,访问类型字段,然后从那里开始。

答案 5 :(得分:-3)

但这并不适用于任何语言。同样在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语言中的对象。面向对象的程序包含一些范式,在大型程序中简化了程序员的生活