Я знаю, что это довольно распространенная тема, но насколько легко найти типичный 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
новые смежные s все еще не являются массивомPixel
s.