为什么 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

如果在第一个版本的代码中 sizesizeof(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_insockaddr_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);
}