Я знаю, що це досить поширена тема, але наскільки типовий 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
Більш конкретно:
- Чи має цей код чітко визначену поведінку?
- Якщо так, чи безпечно використовувати повернутий покажчик?
- Якщо так, то до яких інших
Pixel
типів його можна поширити? (послаблюючи обмеження is_trivial? піксель із лише 3 компонентами).
І clang, і gcc оптимізують весь цикл до небуття, чого я хочу. Тепер я хотів би знати, чи це порушує деякі правила C ++ чи ні.
Godbolt посилання, якщо ви хочете пограти з ним.
(зауважте: я не позначав c ++ 17, незважаючи на те std::byte
, що це питання використовується char
)
pixels[some_index]
чи *(pixels + something)
? Це був би UB.
pixels
(P) - не вказівник на об’єкт масиву, а вказівник на єдиний Pixel
. Це означає, що ви можете отримати доступ лише pixels[0]
легально.
Pixel
розміщений новий все ще не є масивомPixel
s.