C++ 重新解释_cast、char*和未定义的行为

C++ 重新解释_cast、char*和未定义的行为,c++,language-lawyer,undefined-behavior,c++17,reinterpret-cast,C++,Language Lawyer,Undefined Behavior,C++17,Reinterpret Cast,什么情况下重新解释\u cast使用char*(或char[N])是未定义的行为,以及何时定义行为?我应该用什么经验法则来回答这个问题 正如我们从中了解到的,以下是未定义的行为: alignas(int) char data[sizeof(int)]; int *myInt = new (data) int; // OK *myInt = 34; // OK int i = *reinterpret_cast<int

什么情况下
重新解释\u cast
使用
char*
(或
char[N]
)是未定义的行为,以及何时定义行为?我应该用什么经验法则来回答这个问题


正如我们从中了解到的,以下是未定义的行为:

alignas(int) char data[sizeof(int)];
int *myInt = new (data) int;           // OK
*myInt = 34;                           // OK
int i = *reinterpret_cast<int*>(data); // <== UB! have to use std::launder
int
的生存期何时开始?是否带有
数据的声明
?如果是,数据的生存期何时结束

  • 如果
    数据
    是指针怎么办

    char* data_ptr = new char[sizeof(int)];
    *reinterpret_cast<int*>(data_ptr) = 4;     // is this UB?
    int i = *reinterpret_cast<int*>(data_ptr); // how about the read?
    
    char*data_ptr=新字符[sizeof(int)];
    *重新解释铸件(数据ptr)=4;//这是UB吗?
    int i=*重新解释铸造(数据ptr);//阅读怎么样?
    
  • 如果我只是在线路上接收结构,并希望根据第一个字节的内容有条件地强制转换它们,该怎么办

    // bunch of handle functions that do stuff with the members of these types
    void handle(MsgType1 const& );
    void handle(MsgTypeF const& );
    
    char buffer[100]; 
    ::recv(some_socket, buffer, 100)
    
    switch (buffer[0]) {
    case '1':
        handle(*reinterpret_cast<MsgType1*>(buffer)); // is this UB?
        break;
    case 'F':
        handle(*reinterpret_cast<MsgTypeF*>(buffer));
        break;
    // ...
    }
    
    //处理这些类型成员的一组句柄函数
    无效句柄(MsgType1常量&);
    无效句柄(msgtypefconst&);
    字符缓冲区[100];
    ::recv(一些插槽,缓冲区,100)
    开关(缓冲区[0]){
    案例“1”:
    句柄(*reinterpret_cast(buffer));//这是UB吗?
    打破
    案例“F”:
    句柄(*重新解释强制转换(缓冲区));
    打破
    // ...
    }
    

  • 这些案例中有任何一个是UB吗?都是吗?这个问题的答案是否在C++11和C++1z之间变化?

    这里有两条规则:

  • [basic.lval]/8,又称严格的别名规则:简单地说,您不能通过指向错误类型的指针/引用访问对象

  • [base.life]/8:简单地说,如果您对不同类型的对象重复使用存储,那么如果不先清洗旧对象,就不能使用指向旧对象的指针

  • 这些规则是区分“内存位置”或“存储区域”与“对象”的重要部分

    您所有的代码示例都会遇到相同的问题:它们不是您将它们投射到的对象:

    创建类型为
    char[sizeof(int)]
    的对象。该对象不是
    int
    。因此,您可能无法像访问它一样访问它。不管是读还是写;你还在挑衅我

    同样地:

    这也会创建一个类型为
    char[sizeof(int)]
    的对象

    这将创建类型为
    char[100]
    的对象。该对象既不是
    MsgType1
    也不是
    MsgTypeF
    。因此,您无法访问它,就好像它也是

    请注意,这里的UB是作为
    Msg*
    类型之一访问缓冲区时出现的,而不是在检查第一个字节时出现的。如果所有的
    Msg*
    类型都是可复制的,那么完全可以读取第一个字节,然后将缓冲区复制到适当类型的对象中

    switch (buffer[0]) {
    case '1':
        {
            MsgType1 msg;
            memcpy(&msg, buffer, sizeof(MsgType1);
            handle(msg);
        }
        break;
    case 'F':
        {
            MsgTypeF msg;
            memcpy(&msg, buffer, sizeof(MsgTypeF);
            handle(msg);
        }
        break;
    // ...
    }
    
    请注意,我们正在讨论哪些语言状态将是未定义的行为。很有可能编译器对其中任何一个都不会有问题

    这个问题的答案是否在C++11和C++1z之间变化


    自C++11(特别是[basic.life])以来,已经有一些重要的规则澄清。但规则背后的意图并未改变。

    (1)在我看来是有效的。在这两条语句中,一个新的
    int
    对象被变戏法并赋值。读取该值是事情开始变得棘手的地方。与(2)相同(假设
    sizeof(int)==4
    )。(3) 在我看来是UB。@IgorTandetnik也通过阅读充实了这些问题,并且摆脱了关于
    sizeof(int)
    的假设,谢谢。现在(1)和(2)似乎表现出UB,与链接的问题相同。通过保存第一次强制转换中的指针,并将其用于所有后续的写入和读取,可以很容易地进行补救。似乎大多数编译器的行为都与您期望的一样,即使它没有精确定义。在这里查看更多信息:@user2296177:与标记为
    语言律师的问题无关
    ;-]声明我的
    char
    数组是否不可能构成为一些尚未被真空初始化的类型
    t
    获取存储?从这个意义上说,一次健康的清洗不会让每件事都变得清晰吗?@Barry:。如果在旧对象的存储器中启动对象的生存期,则可以从指向旧对象的指针获取指向新对象的指针。它不会开始任何事情的生命。“声明我的char数组难道不可能构成为某些尚未被真空初始化的类型t获取存储吗?”根据该逻辑,任何对象都可以是“尚未被真空初始化的类型t”。毕竟,对象有存储空间
    char[X]
    与任何其他对象一样,都是一个对象。但[basic.life]表示对象的生命周期从获取存储开始。给定
    charbuf[4];int*i=新(buf)int
    ,由
    i
    指向的
    int
    的生存期何时开始?@Barry:Placement new开始对象的生存期,即使该存储中已经有对象。第一条语句将
    char[4]
    放入该存储器中。第二条语句结束
    char[4]
    的生存期,并开始
    int
    的生存期。placement new是否“获取存储”?仓库已经在那里了。
    alignas(int) char data[sizeof(int)];
    
    char* data_ptr = new char[sizeof(int)];
    
    char buffer[100];
    
    switch (buffer[0]) {
    case '1':
        {
            MsgType1 msg;
            memcpy(&msg, buffer, sizeof(MsgType1);
            handle(msg);
        }
        break;
    case 'F':
        {
            MsgTypeF msg;
            memcpy(&msg, buffer, sizeof(MsgTypeF);
            handle(msg);
        }
        break;
    // ...
    }