为什么 reinterpret_cast 在 memcpy 工作时失败?
Why reinterpret_cast fails while memcpy works?
我正在编写一些套接字代码,并根据一些参数使用 IPv4 或 IPv6。为此,我有这样的代码:
struct sockaddr final_addr;
...
struct sockaddr_in6 addr6;
...
memcpy(&final_addr, &addr6, size);
...
bind(fd, &final_addr, size);
这很好用。但是,如果我这样做(这是我最初的想法)
struct sockaddr final_addr;
...
struct sockaddr_in6 addr6;
...
final_addr = *reinterpret_cast<struct sockaddr*>(&addr6);
...
bind(fd, &final_addr, size);
然后它在 bind
上失败并出现 Cannot assign requested address
错误。
请注意,如果我切换到 IPv4 sockaddr_in
,这个不正确的代码可以正常工作。
这是怎么回事?为什么我不能将 sockaddr_in6
重新解释为 sockaddr
?
如果在第一个版本的代码中 size
是 sizeof(addr6)
(如您在评论中所述),那么第一个版本的代码使用 memcpy
复制 sizeof(struct sockaddr_in6)
字节的数据。
第二个版本的代码使用常规 struct sockaddr
赋值来仅复制 sizeof(struct sockaddr)
个字节。
sizeof(struct sockaddr)
小于sizeof(struct sockaddr_in6)
,这使得这两个代码示例不同。
请注意,在第一个版本中,memcpy
中的收件人对象是 struct sockaddr
类型的,即它小于复制的字节数。发生内存溢出,破坏存储在相邻内存位置的一些其他数据。代码 "works" 纯属偶然。 IE。如果这个位 "works",那么其他一些代码(依赖于现在被破坏的数据的代码)可能会失败。
sockaddr
不够大,无法保存来自 sockaddr_in6
的数据。第一个代码示例 "works" 只是因为源地址数据被完整复制,并且您将完整地址传递给 bind()
,但是在复制过程中您还破坏了堆栈内存。第二个代码示例不起作用,因为它在分配期间截断了地址数据,但它不再破坏堆栈内存。
对于 IPv6,这两个代码示例都不能正常工作,但是对于 IPv4,两者都 "work" 可以,因为 sockaddr
足够大,可以保存来自 sockaddr_in
的数据,所以不会丢弃或截断正在发生。
为了确保 final_addr
足够大以容纳来自 sockaddr_in
或 sockaddr_in6
的数据,需要将其声明为 sockaddr_storage
,这保证足够大以容纳来自 any sockaddr_...
结构类型的数据:
struct sockaddr_storage final_addr;
int size;
if (use IPv6)
{
struct sockaddr_in6 addr6;
// populate addr6 as needed...
memcpy(&final_addr, &addr6, sizeof(addr6));
or
*reinterpret_cast<struct sockaddr_in6*>(&final_addr) = addr6;
size = sizeof(addr6);
}
else
{
struct sockaddr_in addr4;
// populate addr4 as needed...
memcpy(&final_addr, &addr4, sizeof(addr4));
or
*reinterpret_cast<struct sockaddr_in*>(&final_addr) = addr4;
size = sizeof(addr4);
}
bind(fd, reinterpret_cast<struct sockaddr*>(&final_addr), size);
更好的选择是使用 getaddrinfo()
或等价物为您创建合适的 sockaddr_...
内存块:
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_NUMERICHOST;
hints.ai_family = AF_UNSPEC;
struct addrinfo *addr = NULL;
if (getaddrinfo("ip address here", "port here", &hints, &addr) == 0)
{
bind(fd, addr->ai_addr, addr->ai_addrlen);
freeaddrinfo(addr);
}
或者:
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_PASSIVE;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = ...; // SOCK_STREAM, SOCK_DGRAM, etc...
hints.ai_protocol = ...; // IPPROTO_TCP, IPPROTO_UDP, etc...
struct addrinfo *addrs = NULL;
if (getaddrinfo(NULL, "port here", &hints, &addrs) == 0)
{
for (struct addrinfo *addr = addrs; addr != NULL; addr = addr->ai_next)
{
int fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
if (fd != -1)
{
bind(fd, addr->ai_addr, addr->ai_addrlen);
// save fd somewhere for later use
...
}
}
freeaddrinfo(addrs);
}
我正在编写一些套接字代码,并根据一些参数使用 IPv4 或 IPv6。为此,我有这样的代码:
struct sockaddr final_addr;
...
struct sockaddr_in6 addr6;
...
memcpy(&final_addr, &addr6, size);
...
bind(fd, &final_addr, size);
这很好用。但是,如果我这样做(这是我最初的想法)
struct sockaddr final_addr;
...
struct sockaddr_in6 addr6;
...
final_addr = *reinterpret_cast<struct sockaddr*>(&addr6);
...
bind(fd, &final_addr, size);
然后它在 bind
上失败并出现 Cannot assign requested address
错误。
请注意,如果我切换到 IPv4 sockaddr_in
,这个不正确的代码可以正常工作。
这是怎么回事?为什么我不能将 sockaddr_in6
重新解释为 sockaddr
?
如果在第一个版本的代码中 size
是 sizeof(addr6)
(如您在评论中所述),那么第一个版本的代码使用 memcpy
复制 sizeof(struct sockaddr_in6)
字节的数据。
第二个版本的代码使用常规 struct sockaddr
赋值来仅复制 sizeof(struct sockaddr)
个字节。
sizeof(struct sockaddr)
小于sizeof(struct sockaddr_in6)
,这使得这两个代码示例不同。
请注意,在第一个版本中,memcpy
中的收件人对象是 struct sockaddr
类型的,即它小于复制的字节数。发生内存溢出,破坏存储在相邻内存位置的一些其他数据。代码 "works" 纯属偶然。 IE。如果这个位 "works",那么其他一些代码(依赖于现在被破坏的数据的代码)可能会失败。
sockaddr
不够大,无法保存来自 sockaddr_in6
的数据。第一个代码示例 "works" 只是因为源地址数据被完整复制,并且您将完整地址传递给 bind()
,但是在复制过程中您还破坏了堆栈内存。第二个代码示例不起作用,因为它在分配期间截断了地址数据,但它不再破坏堆栈内存。
对于 IPv6,这两个代码示例都不能正常工作,但是对于 IPv4,两者都 "work" 可以,因为 sockaddr
足够大,可以保存来自 sockaddr_in
的数据,所以不会丢弃或截断正在发生。
为了确保 final_addr
足够大以容纳来自 sockaddr_in
或 sockaddr_in6
的数据,需要将其声明为 sockaddr_storage
,这保证足够大以容纳来自 any sockaddr_...
结构类型的数据:
struct sockaddr_storage final_addr;
int size;
if (use IPv6)
{
struct sockaddr_in6 addr6;
// populate addr6 as needed...
memcpy(&final_addr, &addr6, sizeof(addr6));
or
*reinterpret_cast<struct sockaddr_in6*>(&final_addr) = addr6;
size = sizeof(addr6);
}
else
{
struct sockaddr_in addr4;
// populate addr4 as needed...
memcpy(&final_addr, &addr4, sizeof(addr4));
or
*reinterpret_cast<struct sockaddr_in*>(&final_addr) = addr4;
size = sizeof(addr4);
}
bind(fd, reinterpret_cast<struct sockaddr*>(&final_addr), size);
更好的选择是使用 getaddrinfo()
或等价物为您创建合适的 sockaddr_...
内存块:
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_NUMERICHOST;
hints.ai_family = AF_UNSPEC;
struct addrinfo *addr = NULL;
if (getaddrinfo("ip address here", "port here", &hints, &addr) == 0)
{
bind(fd, addr->ai_addr, addr->ai_addrlen);
freeaddrinfo(addr);
}
或者:
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_PASSIVE;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = ...; // SOCK_STREAM, SOCK_DGRAM, etc...
hints.ai_protocol = ...; // IPPROTO_TCP, IPPROTO_UDP, etc...
struct addrinfo *addrs = NULL;
if (getaddrinfo(NULL, "port here", &hints, &addrs) == 0)
{
for (struct addrinfo *addr = addrs; addr != NULL; addr = addr->ai_next)
{
int fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
if (fd != -1)
{
bind(fd, addr->ai_addr, addr->ai_addrlen);
// save fd somewhere for later use
...
}
}
freeaddrinfo(addrs);
}