【问题标题】:Why reinterpret_cast fails while memcpy works?为什么在 memcpy 工作时 reinterpret_cast 失败?
【发布时间】:2016-10-06 17:07:41
【问题描述】:

我正在编写一些套接字代码,并且基于一些参数,我正在使用 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

【问题讨论】:

  • sockaddr 是一个不透明的指针,可以备份sockaddr_in (IPv4) 或sockaddr_in6 (IPv6)。除了sin_family 字段之外,这些结构完全不同。 reinterpret_castmemcpy() 都不能正常工作以从一个转换为另一个。
  • sockaddr_in6 的定义中是否有 const 关键字?
  • 我想你误会了。我已经删除了大部分初始化代码。该代码与 memcpy 一起工作正常(套接字绑定并正常工作)。但是 reinterpret_cast 失败了。
  • 它可能是 sockaddr 定义的某种限定符。你能让你的 IDE 找到 sockaddr 的定义吗?
  • @πάνταῥεῖ 我不是专家,但我不认为你是正确的。我见过的每个资源都会在bind 上将sockaddr_in6* 转换为sockaddr*(例如stackoverflow.com/questions/13504934/…)否则你怎么称呼bind?还是通过() 进行的演员有什么不同?

标签: c++ sockets reinterpret-cast


【解决方案1】:

如果在代码的第一个版本中 sizesizeof(addr6)(如您在 cmets 中所述),则代码的第一个版本使用 memcpy 复制 sizeof(struct sockaddr_in6) 字节的数据。

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

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

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

【讨论】:

  • "works" 意味着你写的东西是......在堆栈中更靠前?所以你覆盖了之前分配的堆栈上的东西?在一个足够糟糕的情况下,它会写满你的返回码地址,对吧?
  • 另外,你也会受到打击。
【解决方案2】:

sockaddr 不够大,无法容纳来自sockaddr_in6 的数据。第一个代码示例“有效”只是因为源地址数据被完整复制,并且您将完整地址传递给bind(),但在复制过程中您还破坏了堆栈内存。第二个代码示例不起作用,因为它在分配期间截断了地址数据,但它不再破坏堆栈内存。

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

为确保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);
}

【讨论】:

    猜你喜欢
    • 2018-08-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-20
    • 1970-01-01
    • 1970-01-01
    • 2022-07-06
    • 2010-09-15
    相关资源
    最近更新 更多