使用有效的C代码将struct sockaddr *转换为struct sockaddr_in6 *的正确方法是什么?

这是一个简单的程序,它显示了在编写套接字程序时我们通常如何将struct sockaddr *类型struct sockaddr * struct sockaddr_in *struct sockaddr_in6 *

 #include  #include  #include  #include  #include  #include  int main() { struct addrinfo *ai; printf("sizeof (struct sockaddr): %zu\n", sizeof (struct sockaddr)); printf("sizeof (struct sockaddr_in): %zu\n", sizeof (struct sockaddr_in)); printf("sizeof (struct sockaddr_in6): %zu\n", sizeof (struct sockaddr_in6)); 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; } 

Beej的网络编程指南也在第10页中推荐了这一点。

为了处理struct sockaddr,程序员创建了一个并行结构:struct sockaddr_in(“in”表示“Internet”)将与IPv4一起使用。

这是重要的一点:指向struct sockaddr_in的指针可以转换为指向struct sockaddr的指针,反之亦然。 所以即使connect()想要一个struct sockaddr *,你仍然可以使用struct sockaddr_in并在最后一刻投出它!

但是从另一个问题的讨论来看,这似乎只是一个黑客,而不是符合C标准的有效C代码。

特别是,请参阅AnT提到的答案 ,

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

因此,如果我们使用这种技术进行套接字编程(以及书籍推荐的内容)是无效的,那么重写上述代码的有效方法是什么,以便它也是符合C标准的有效C代码?

因此,如果我们进行套接字编程的方式(以及书籍推荐的内容)是一个hack,重写上述代码的正确方法是什么,以便它也是符合C标准的有效C代码?

TL; DR:继续做你在你的例子中提出的。

您提供的代码似乎在语法上是正确的。 在某些情况下,它可能会也可能不会表现出未定义的行为。 它是否确实取决于getaddrinfo()的行为。

在C中无法满足所有function要求,并且可以比您提供的标准技术更好地防止未定义的行为。 这就是为什么它是标准技术。 这里的问题是该函数必须支持所有可想到的地址类型,包括尚未定义的类型。 它可以将套接字地址指针声明为void * ,这不需要转换,但这实际上不会改变任何给定程序是否显示未定义的行为。

就其本身而言, getaddrinfo()在设计时考虑到了这样的用法,因此如果在结果上使用预期的强制转换允许不当行为,那就是它的问题。 此外, getaddrinfo()不是C标准库的一部分 – 它由POSIX标准化(仅),它也包含C标准。 因此,根据C单独分析该function表明不适当的超聚焦。 虽然演员阵容仅根据C引起了一些关注,但你应该期望在使用struct sockaddr *getaddrinfo()和其他POSIX网络函数的上下文中,转换为正确的特定地址类型并访问引用的对象会产生可靠的结果。

另外,我认为AnT对你的另一个问题的回答过于简单并且过于消极。 我在考虑是否要写一个对比鲜明的答案。

POSIX标准保证可以将指向任何类型套接字的指针struct sockaddr*转换为struct sockaddr* 。 因此,您可以将指向任何类型的套接字的指针struct sockaddr*转换为struct sockaddr* ,以便在bind()connect()使用它; 库知道要检查哪些位。 您还可以检查套接字的sa_family字段以查看其实际内容,假设它包含有效数据,然后转换为适当的指针类型。 如果需要分配足够大的内存块来安全地存储任何类型的套接字,请使用sockaddr_storage 。 从sockaddr_storage*到任何其他套接字指针的转换保证正确对齐,并且保证包含套接字族的字段仍然有效。

要从sockaddr_in获取IPv6套接字,可以将IPv4地址转换为IPv6表示法并使用getaddrinfo() 。 但是,现代查找function可能会为您提供包含IPv4和IPv6套接字的链接列表。

答案是man getaddrinfosys/socket.hman getaddrinfo提供了使用常见struct sockaddr的理性背后:

 Given node and service, which identify an Internet host and a service, getaddrinfo() returns one or more addrinfo structures, each of which contains an Internet address that can be specified in a call to bind(2) or connect(2). The getaddrinfo() function combines the functionality provided by the gethostbyname(3) and getservbyname(3) functions into a single interface, but unlike the latter functions, getaddrinfo() is reentrant and allows programs to eliminate IPv4-versus-IPv6 dependencies. 

只有一个struct sockaddr 。 看起来各种类型都只是在透明联合中使用,以提供所需的任何struct sockaddr_X 。 例如:

 /* This is the type we use for generic socket address arguments. With GCC 2.7 and later, the funky union causes redeclarations or uses with any of the listed types to be allowed without complaint. G++ 2.7 does not support transparent unions so there we want the old-style declaration, too. */ #if defined __cplusplus || !__GNUC_PREREQ (2, 7) || !defined __USE_GNU # define __SOCKADDR_ARG struct sockaddr *__restrict # define __CONST_SOCKADDR_ARG const struct sockaddr * #else /* Add more `struct sockaddr_AF' types here as necessary. These are all the ones I found on NetBSD and Linux. */ # define __SOCKADDR_ALLTYPES \ __SOCKADDR_ONETYPE (sockaddr) \ __SOCKADDR_ONETYPE (sockaddr_at) \ __SOCKADDR_ONETYPE (sockaddr_ax25) \ __SOCKADDR_ONETYPE (sockaddr_dl) \ __SOCKADDR_ONETYPE (sockaddr_eon) \ __SOCKADDR_ONETYPE (sockaddr_in) \ __SOCKADDR_ONETYPE (sockaddr_in6) \ __SOCKADDR_ONETYPE (sockaddr_inarp) \ __SOCKADDR_ONETYPE (sockaddr_ipx) \ __SOCKADDR_ONETYPE (sockaddr_iso) \ __SOCKADDR_ONETYPE (sockaddr_ns) \ __SOCKADDR_ONETYPE (sockaddr_un) \ __SOCKADDR_ONETYPE (sockaddr_x25) # define __SOCKADDR_ONETYPE(type) struct type *__restrict __##type##__; typedef union { __SOCKADDR_ALLTYPES } __SOCKADDR_ARG __attribute__ ((__transparent_union__)); # undef __SOCKADDR_ONETYPE # define __SOCKADDR_ONETYPE(type) const struct type *__restrict __##type##__; typedef union { __SOCKADDR_ALLTYPES } __CONST_SOCKADDR_ARG __attribute__ ((__transparent_union__)); # undef __SOCKADDR_ONETYPE #endif 

虽然我没有吃过所有的浓汤,但是看起来你很安全。

参考这个和另一个链接对于不同结构类型的类型转换指针是否合法(例如struct sockaddr * to struct sockaddr_in6 *)? 。 这些并不完全是黑客攻击。 要做你想做的事,如果正确理解,我会做以下事情:

 struct base { int a; char b; double *n; } struct derived { struct base b; //(no pointer, but the whole struct) int c; int d; } 

这样,当您从派生到基础转换时,您确定派生的前n个字节与基础完全重叠。 代码工作并且完全可移植。 不同的问题不同解决方案。 实际上根据我的经验,我曾经倾向于基础包含派生,而不是反之亦然。 所以要有一个“多态”结构。 但是1)如果它有效,2)人们将阅读代码将会理解3)你觉得有用…为什么不呢? 听你的。 可能c ++正是以这种方式实现了hinerhitance! 谁能说出来?
只需要小心它们的数组,使用正确的类型进行索引,并小心将它放在第一位。 (但是C ++也遇到了多态对象数组的问题,它只能指出它们的指针)