Почему reinterpret_cast не работает, пока работает memcpy?

Я пишу код сокета и, основываясь на некоторых параметрах, использую 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?

2 ответа

Решение

Если в первой версии кода size является sizeof(addr6) (как вы указали в комментариях), тогда первая версия кода использует memcpy копировать sizeof(struct sockaddr_in6) байты данных.

Вторая версия кода использует обычные struct sockaddr назначение только для копирования sizeof(struct sockaddr) байт.

sizeof(struct sockaddr) меньше чем sizeof(struct sockaddr_in6), что делает эти два примера кода разными.

Обратите внимание, что в первой версии объект получателя в этом memcpy имеет struct sockaddr тип, т.е. он меньше, чем количество скопированных байтов. Происходит переполнение памяти, что забивает некоторые другие данные, хранящиеся в соседних ячейках памяти. Код "работает" только случайно. Т.е. если этот бит "работает", то какой-то другой фрагмент кода (тот, который опирается на данные, перенесенные в настоящее время), скорее всего потерпит неудачу.

sockaddr недостаточно велика для хранения данных из sockaddr_in6, Первый пример кода "работает" только потому, что данные исходного адреса копируются полностью, а вы передаете полный адрес bind(), но вы также уничтожили память стека во время копирования. Второй пример кода не работает, потому что он усекает адресные данные во время присваивания, но больше не уничтожает память стека.

Ни один из примеров кода не будет работать правильно для IPv6, но оба будут "работать" нормально для IPv4, потому что sockaddr достаточно велик, чтобы хранить данные из sockaddr_inтаким образом, не происходит подмешивание или усечение.

Для обеспечения final_addr достаточно велик, чтобы хранить данные либо sockaddr_in или же sockaddr_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);
}
Другие вопросы по тегам