为什么在memcpy工作时reinterpret_cast失败?

时间:2016-10-06 17:07:42

标签: c++ sockets reinterpret-cast

我正在编写一些套接字代码并基于某些使用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);

然后bindCannot assign requested address错误而失败。

请注意,如果我切换到IPv4 sockaddr_in,则此错误代码可以正常工作。

这里发生了什么?为什么我不能将sockaddr_in6重新解释为sockaddr

2 个答案:

答案 0 :(得分:2)

如果代码size的第一个版本是sizeof(addr6)(正如您在评论中所述),则代码的第一个版本使用memcpy复制{{1} }数据字节。

代码的第二个版本使用常规sizeof(struct sockaddr_in6)赋值来仅复制struct sockaddr个字节。

sizeof(struct sockaddr)小于sizeof(struct sockaddr),这使得这两个代码示例不同。

请注意,在第一个版本中,sizeof(struct sockaddr_in6)中的收件人对象属于memcpy类型,即它小于复制的字节数。发生内存溢出,这会破坏存储在相邻内存位置的其他一些数据。代码“只能偶然”起作用。即如果这个位“有效”,那么另一段代码(依赖于现在被破坏的数据的代码)可能会失败。

答案 1 :(得分:1)

sockaddr不足以保存sockaddr_in6的数据。第一个代码示例“工作”只是因为源地址数据被完整复制,并且您将完整地址传递给bind(),但您在复制期间也已经破坏了堆栈内存。第二个代码示例不起作用,因为它在分配期间截断了地址数据,但它不再破坏堆栈内存。

这两个代码示例都不能正常用于IPv6,但两者都可以“正常”用于IPv4,因为sockaddr足够大以容纳来自sockaddr_in的数据,因此不会发生垃圾或截断。< / p>

为了确保final_addr足够大以容纳来自sockaddr_insockaddr_in6的数据,需要将其声明为sockaddr_storage,这可以保证很大足以保存来自任何 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);
}