Вариация на тему типа прокалывания: на месте тривиальная конструкция - PullRequest
9 голосов
/ 14 января 2020

Я знаю, что это довольно распространенная тема, но насколько легко найти типичный 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, и g cc оптимизируют весь l oop до небытия, чего я и хочу. Теперь я хотел бы знать, нарушает ли это некоторые правила C ++ или нет.

Godbolt link , если вы хотите поиграть с ним.

(примечание: I не пометил c ++ 17, несмотря на std::byte, потому что вопрос имеет значение char)

Ответы [ 2 ]

3 голосов
/ 14 января 2020

Неопределенное поведение использовать результат promote в качестве массива. Если мы посмотрим на [expr.add] /4.2, то получим

В противном случае , если P указывает на элемент массива i объекта массива x с n элементами ([dcl.array]), выражения P + J и J + P (где J имеет значение j) указывают на (возможно, гипотетический) элемент массива i+j из x, если 0≤i+j≤n, и выражение P - J указывает на (возможно, гипотетический) элемент массива i−j из x, если 0≤i−j≤n.

, мы видим, что требуется указатель, чтобы фактически указывать на объект массива. На самом деле у вас нет объекта массива. У вас есть указатель на один Pixel, за которым случайно следует другой Pixels в непрерывной памяти. Это означает, что единственный элемент, к которому вы можете получить доступ, это первый элемент. Попытка получить доступ ко всему остальному была бы неопределенным поведением, потому что вы вышли за пределы допустимого домена для указателя.

1 голос
/ 14 января 2020

У вас уже есть ответ относительно ограниченного использования возвращаемого указателя, но я хочу добавить, что я также думаю, что вам нужно std::launder, чтобы даже иметь доступ к первым Pixel:

reinterpret_cast выполняется до создания любого Pixel объекта (при условии, что вы не делаете это в getSomeImageData). Поэтому reinterpret_cast не изменит значение указателя. Полученный указатель будет по-прежнему указывать на первый элемент массива std::byte, переданный функции.

Когда вы создаете объекты Pixel, они будут вложенными в пределах Массив std::byte и массив std::byte будут , обеспечивая хранилище для Pixel объектов.

В некоторых случаях повторное использование хранилища приводит к тому, что указатель на старый объект автоматически указывает на новый объект. Но это не то, что здесь происходит, поэтому result все равно будет указывать на объект std::byte, а не на объект Pixel. Я предполагаю, что использование его так, как если бы оно указывало на Pixel объект, технически будет неопределенным поведением.

Я думаю, что это сохраняется, даже если вы делаете reinterpret_cast после создания Pixel объект, поскольку объект Pixel и std::byte, который предоставляет хранилище для него, не являются взаимозаменяемыми по указателю . Таким образом, даже тогда указатель будет продолжать указывать на std::byte, а не на объект Pixel.

Если вы получили указатель для возврата из результата одного из новых мест размещения, то все должно быть в порядке что касается доступа к этому указанному c Pixel объекту.


Также необходимо убедиться, что указатель std::byte правильно выровнен для Pixel и что массив действительно достаточно велик. Насколько я помню, стандарт не требует, чтобы Pixel имел такое же выравнивание, как std::byte, или чтобы он не имел отступов.


Также все это не зависит от Pixel быть тривиальным или действительно любым другим свойством этого. Все будет вести себя так же, пока массив std::byte имеет достаточный размер и соответствующим образом выровнен для объектов Pixel.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...