用sockaddr安全reinterpret_cast?

时间:2017-05-27 20:37:05

标签: c++ undefined-behavior type-safety reinterpret-cast

假设我正在进行一些套接字编程:

import pandas as pd

def read_data(file, sheetname=0):
    df = pd.read_excel(file, sheetname, engine='xlrd')

    return(df)

现在问题是:我们做了struct sockaddr_in sa; inet_pton(AF_INET, "127.0.0.1", &(sa.sin_addr)); auto *resa = reinterpret_cast<struct sockaddr*>(&sa); bind(sfd, resa, sizeof(sa)); (或者像教程或男人那样的C风格的reinterpret_cast演员)但标准并不保证能够正常工作,对吗?另一方面,似乎没有办法以不同的方式做到这一点,(struct sockaddr *)需要bind()(并且它必须访问底层结构以确定它收到的内容)。

在这种情况下,在不同类型之间进行struct sockaddr*是否安全?如果是,那么为什么?

5 个答案:

答案 0 :(得分:2)

该标准将支持多种指针操作的能力视为实现质量问题。该标准并没有要求所有实施都适合于低级或系统编程,而是规定了适合于此目的的高质量实施,例如: Unix应该支持这种平台上的系统代码通常使用的各种语义。一个实现可能无法处理以类型不可知的方式处理结构的共享部分的代码,但对于某些不涉及任何低级或系统编程的特殊目的,它仍然是高质量的实现 >。另一方面,适合于低级编程的高质量实现应毫不费力地处理此类代码。任何不能处理此类代码的实现都应被视为是低质量的实现,并且/或者不适合用于低级编程,并且低级程序无法使用此类实现也不是缺陷。

答案 1 :(得分:2)

  

在这种情况下,在不同类型之间进行reinterpret_cast可以安全吗?

不,不是。您可以使用指向sockaddr的指针来指向类型为sockaddr_in的对象。这些是不相关的类型,因此暗示着不正确的内容:指向两个不相关的对象,但只分配了一个对象。

如果您在最受限的系统上工作,那么是的,您可能对此感到满意,正如@supercat所说,您的实现者可能会支持您。但是您的代码将无法移植。

  

似乎没有办法做到这一点

规定的解决方案是为两个对象分配内存,并使用std::memcpy使它们相等:

sockaddr sa2;
std::memcpy(&sa2, &sa, sizeof(sa));
bind(sfd, &sa2, sizeof(sa));

来自cppreference.com

  

如果严格的别名禁止检查与两种不同类型的值相同的内存,则可以使用std::memcpy来转换值。

std::memcpy的呼叫并非总是免费的,但通常是免费的。 (example

答案 2 :(得分:1)

与最上面的答案相反,我想说 bind CAN and MUST 的编写方式使得 reinterpret_cast 在这里使用是安全的。例如,bind 可以实现为:

int bind(SOCKET s, const sockaddr* addr, int addrlen) {
    std::uint16_t address_family;
    std::memcpy(&address_family, addr, sizeof(address_family));
    if (address_family == AF_INET) {
        const sockaddr_in* sin = reinterpret_cast<const sockaddr_in*>(addr);
        // Accessing sin->sin_addr is safe here
        ...
    } else if (address_family == AF_INET6) {
        const sockaddr_in6* sin6 = reinterpret_cast<const sockaddr_in6*>(addr);
        ...
    }
}

关键是 reinterpret_cast 本身不是 UB,它试图访问属于 UB 的数据(参见 Type Aliasing):

<块引用>

每当尝试通过 AliasedType 类型的泛左值读取或修改 DynamicType 类型对象的存储值时,行为都是未定义的[...]

在上面的代码中,我们从不尝试通过类型为 addr 的指针读取 sockaddr* 的内容。我们检查值表示(原始字节)以获取地址族,它告诉我们要使用的结构的确切类型。然后我们可以安全地转换回原始类型。标准允许强制转换为不同类型的指针,然后返回原始类型。

更进一步,我会说使用 sockaddr_in6 时,实现必须正确处理 reinterpret_cast。自 sizeof(sockaddr_in6) > sizeof(sockaddr) 起,memcpy 技巧不再有效。 API 特别要求提供指向错误类型对象的指针,因此 API 实现者有责任正确使用该指针。

答案 3 :(得分:0)

考虑到在示例代码中看到转换为struct sockaddr*,并且您将其传递给API函数(对于记录良好且经过良好测试的库),如果违反前提条件,则只是不安全功能。

投放是必要的,因为sockaddr(例如sockaddr_insockaddr_un)有不同类型,只有一种bind功能。

C ++编译器在进行C风格转换时会选择reinterpret_cast,但最好是为了便于阅读。

答案 4 :(得分:-2)

是的,这是绝对安全和正确的。

  

使用 sockaddr 的Winsock函数并未严格解释为   指向sockaddr结构的指针。结构被解释   在不同的地址族的背景下不同。唯一的   要求是第一个 u_short 是地址系列和   内存缓冲区的总大小(以字节为单位)是namelen。

所以你需要将指针传递给从u_short sin_family;开始的某个位置。 sockaddr_in符合这个条件。 reinterpret_cast未将您的指针更改为sockaddr_in sa;并且不生成任何二进制代码:reinterpret_cast<sockaddr*>(&sa);(sockaddr*)&sa&sa相同。换句话说,二进制指针 &sa == reinterpret_cast<sockaddr*>(&sa)

所以你可以而且必须使用

 bind(sfd, reinterpret_cast<sockaddr*>(&sa), sizeof(sa));

在这里使用auto *resa毫无意义,为什么?

或者例如我们可以使用下一个代码:

union {
    sockaddr sa;
    sockaddr_in sa_in;
};

sa_in.sin_family = AF_INET;
sa_in.sin_port = *;
sa_in.sin_addr.S_un.S_addr = *;
bind(0, &sa, sizeof(sa_in));

and main - 尝试将自己置于编写bind函数的位置。它如何与sockaddr一起使用?显然它首先看它sa_family,然后基于它sa_familyreinterpret_cast它更具体的结构