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
)
将结果用作promote
数组是未定义的行为。如果我们看一下[expr.add]/4.2我们有
\n\n\n否则,if
\nP
指向具有i
x
元素([dcl.array])的数组对象的n
数组元素,则表达式P + J
和J + P
(其中J
有值j
)指向 ifi+j
的(可能是假设的)数组元素 \n并且表达式指向if的(可能是假设的)数组元素 \nx
0\xe2\x89\xa4i+j\xe2\x89\xa4n
P - J
i\xe2\x88\x92j
x
0\xe2\x89\xa4i\xe2\x88\x92j\xe2\x89\xa4n
。
我们看到它需要指针实际指向一个数组对象。但实际上你并没有数组对象。你有一个指向单个的指针,Pixel
而这个指针恰好有其他的Pixels
在连续内存中紧随其后。这意味着您实际可以访问的唯一元素是第一个元素。尝试访问其他任何内容将是未定义的行为,因为您已经超出了指针有效域的末尾。