Sé que este es un tema bastante común, pero aunque la UB típica es fácil de encontrar, no encontré esta variante hasta ahora.
Por lo tanto, estoy tratando de presentar formalmente los objetos Pixel mientras evito una copia real de los datos.
¿Es esto válido?
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.
}
Patrón de uso esperado, muy simplificado:
std::byte * buffer = getSomeImageData();
auto pixels = promote(buffer, 800*600);
// manipulate pixel data
Más específicamente:
- ¿Este código tiene un comportamiento bien definido?
- En caso afirmativo, ¿es seguro usar el puntero devuelto?
- En caso afirmativo, ¿a qué otros
Pixel
tipos se puede extender? (¿relajando la restricción is_trivial? ¿píxel con solo 3 componentes?).
Tanto clang como gcc optimizan todo el ciclo a la nada, que es lo que quiero. Ahora, me gustaría saber si esto viola algunas reglas de C ++ o no.
Enlace Godbolt si quieres jugar con él.
(nota: no etiqueté c ++ 17 a pesar de std::byte
que la pregunta se mantiene usando char
)
pixels[some_index]
o *(pixels + something)
? Eso sería UB.
pixels
(P) no es un puntero al objeto de matriz, sino un puntero a un solo Pixel
. Eso significa que solo puede acceder pixels[0]
legalmente.
Pixel
s colocado nuevo todavía no es una matriz dePixel
s.