gpt4 book ai didi

c++ - reinterpret_cast、char* 和未定义的行为

转载 作者:IT老高 更新时间:2023-10-28 23:02:02 24 4
gpt4 key购买 nike

reinterpret_casting a 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

但是在什么时候我们可以对 char 数组执行 reinterpret_cast 并且让它不是未定义的行为?下面是几个简单的例子:

  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;
    // ...
    }

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

最佳答案

这里有两条规则:

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

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

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

您的所有代码示例都遇到了同样的问题:它们不是您将它们转换到的对象:

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;
// ...
}

请注意,我们正在讨论的语言状态将是未定义的行为。编译器很可能对其中任何一个都很好。

Does the answer to this question change between C++11 to C++1z?

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

关于c++ - reinterpret_cast、char* 和未定义的行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39429476/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com