reinterpret_cast,char *和未定义的行为

时间:2016-09-10 18:51:30

标签: c++ language-lawyer undefined-behavior c++17 reinterpret-cast

reinterpret_cast char*(或char[N])是未定义的行为,以及何时定义了行为?我应该用什么经验来回答这个问题?

正如我们从this question学到的,以下是未定义的行为:

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

但是我们可以在reinterpret_cast数组上对char做什么,并且它不是未定义的行为?以下是一些简单的例子:

  1. new,仅reinterpret_cast

    alignas(int) char data[sizeof(int)];
    *reinterpret_cast<int*>(data) = 42;    // is the first cast write UB?
    int i = *reinterpret_cast<int*>(data); // how about a read?
    *reinterpret_cast<int*>(data) = 4;     // how about the second write?
    int j = *reinterpret_cast<int*>(data); // or the second read?
    

    int的生命周期何时开始?这是data的声明吗?如果是这样,data的生命周期何时结束?

  2. 如果data是指针会怎么样?

    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?
    
  3. 如果我只是在线上接收结构并希望根据第一个字节有条件地投射它们怎么办?

    // 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;
    // ...
    }
    
  4. UB中是否有任何一种情况?他们都是?这个问题的答案在C ++ 11到C ++ 1z之间是否会发生变化?

1 个答案:

答案 0 :(得分:5)

这里有两条规则:

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

  2. [base.life] / 8:简单地说,如果你为不同类型的对象重用存储,你就不能使用指向旧对象的指针而不先洗掉它们。 / p>

  3. 这些规则是区分&#34;内存位置&#34;的重要部分。或者&#34;存储区域&#34;和&#34;一个对象&#34;。

    您的所有代码示例都会成为同一问题的牺牲品:它们不是您投射到的对象:

    alignas(int) char data[sizeof(int)];
    

    创建一个char[sizeof(int)]类型的对象。该对象 int。因此,您可能无法像访问它一样访问它。如果它是读或写,则无关紧要;你还挑起UB。

    类似地:

    char* data_ptr = new char[sizeof(int)];
    

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

    char buffer[100];
    

    这会创建一个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])以来,已经有了一些重要的规则澄清。但规则背后的意图并没有改变。