类型双关主题的变体:就地琐碎构造

Variation on the type punning theme: in-place trivial construction

我知道这是一个很常见的主题,但是尽管典型的 UB 很容易找到,但我到目前为止还没有找到这个变体。

因此,我试图正式引入 Pixel 对象,同时避免实际复制数据。

这有效吗?

struct Pixel {
    uint8_t red;
    uint8_t green;
    uint8_t blue;
    uint8_t alpha;
};

static_assert(std::is_trivial_v<Pixel>);

Pixel* promote(std::byte* data, std::size_t count)
{
    Pixel * const result = reinterpret_cast<Pixel*>(data);
    while (count-- > 0) {
        new (data) Pixel{
            std::to_integer<uint8_t>(data[0]),
            std::to_integer<uint8_t>(data[1]),
            std::to_integer<uint8_t>(data[2]),
            std::to_integer<uint8_t>(data[3])
        };
        data += sizeof(Pixel);
    }
    return result; // throw in a std::launder? I believe it is not mandatory here.
}

预期使用模式,高度简化:

std::byte * buffer = getSomeImageData();
auto pixels = promote(buffer, 800*600);
// manipulate pixel data

更具体地说:

clang 和 gcc 都将整个循环优化为无,这就是我想要的。现在,我想知道这是否违反了某些 C++ 规则。

Godbolt link如果你想玩一下。

(注意:尽管 std::byte,我没有标记 c++17,因为问题仍然存在 char

promote的结果作为数组使用是未定义的行为。如果我们查看 [expr.add]/4.2 我们有

Otherwise, if P points to an array element i of an array object x with n elements ([dcl.array]), the expressions P + J and J + P (where J has the value j) point to the (possibly-hypothetical) array element i+j of x if 0≤i+j≤n and the expression P - J points to the (possibly-hypothetical) array element i−j of x if 0≤i−j≤n.

我们看到它需要指针实际指向一个数组对象。你实际上并没有数组对象。你有一个指向单个 Pixel 的指针,它恰好在连续内存中有其他 Pixels 跟随它。这意味着您实际可以访问的唯一元素是第一个元素。尝试访问其他任何内容都将是未定义的行为,因为您已经超出了指针有效域的末尾。

关于 returned 指针的有限使用,您已经有了答案,但我想补充一点,我还认为您需要 std::launder 才能访问第一个 Pixel:

reinterpret_cast 在创建任何 Pixel 对象之前完成(假设您在 getSomeImageData 中不这样做)。因此 reinterpret_cast 不会改变指针值。结果指针仍将指向传递给函数的 std::byte 数组的第一个元素。

当您创建 Pixel 对象时,它们将 嵌套 std::byte 数组中,并且 std::byte 数组将是Pixel 个对象提供存储

有些情况下,存储的重用会导致指向旧对象的指针自动指向新对象。但这不是这里发生的事情,所以 result 仍将指向 std::byte 对象,而不是 Pixel 对象。我想使用它就好像它指向一个 Pixel 对象在技术上将是未定义的行为。

我认为这仍然成立,即使您在创建 Pixel 对象后执行 reinterpret_cast,因为 Pixel 对象和提供存储的 std::byte因为它不是 pointer-interconvertible。所以即便如此,指针仍将指向 std::byte,而不是 Pixel 对象。

如果您从其中一个 placement-new 的结果中获得指向 return 的指针,那么就访问特定 Pixel 对象而言,一切都应该没问题。


您还需要确保 std::byte 指针与 Pixel 适当对齐,并且数组确实足够大。据我所知,该标准并不真正要求 Pixelstd::byte 具有相同的对齐方式或者它没有填充。


此外 none 这取决于 Pixel 是微不足道的还是它的任何其他 属性。只要 std::byte 数组有足够的大小并且适合 Pixel 对象对齐,一切都会以相同的方式运行。