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

spe*_*ras 9 c++ placement-new object-lifetime language-lawyer reinterpret-cast

我知道这是一个很常见的主题,但尽管很容易找到典型的 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.
}
Run Code Online (Sandbox Code Playgroud)

预期使用模式,大大简化:

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

进一步来说:

  • 这段代码有明确定义的行为吗?
  • 如果是,使用返回的指针是否安全?
  • 如果是,Pixel它可以扩展到哪些其他类型?(放宽 is_trivial 限制?只有 3 个分量的像素?)。

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

如果你想玩它,Godbolt 链接

(注意:尽管使用std::byte,我没有标记 c++17 ,因为问题成立char

Nat*_*ica 3

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

\n\n
\n

否则,ifP指向具有ix元素([dcl.array])的数组对象的n数组元素,则表达式P + JJ + P(其中J有值j)指向 ifi+j的(可能是假设的)数组元素 \n并且表达式指向if的(可能是假设的)数组元素 \n x0\xe2\x89\xa4i+j\xe2\x89\xa4nP - Ji\xe2\x88\x92jx0\xe2\x89\xa4i\xe2\x88\x92j\xe2\x89\xa4n

\n
\n\n

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

\n