Понимание примера кода с reinterpret_cast из POD-структуры
Я нашел некоторый код и хочу убедиться, что я правильно понимаю это. Вариант использования - это упакованное изображение, которое представлено массивом значений. В этом примере три значения представляют один пиксель.
Код, который я нашел, выглядит примерно так:
struct Pixel{
int[3] data
int x(){return data[0];}
int y(){return data[1];}
int z(){return data[2];}
};
void main(){
std::vector<int> img(300);
Pixel* access = reinterpret_cast<Pixel*>(img.data()+3*5);
foo(access->x());
}
Как я понимаю из чтения POD и стандартного макета, я думаю, что пример кода верен, потому что мы используем только первый член Pixel? Затем замена пикселя на
struct Pixel2{
int red;
int green;
int blue;
};
приведет к неопределенному поведению?
Редактировать: я работаю с CUDA и нашел другой пример: приведение и беззнаковый указатель char (массив) на указатель uchar3. Определение типа uchar3 равно определению второго пикселя. Значит ли это, что второе тоже верно? Или это работает только для кода, скомпилированного nvcc? Если второе определение Pixel является действительным, то почему?
Изменить: Чтобы еще раз подчеркнуть, что код пытается сделать, я переименовал некоторые поля выше: у меня есть массив необработанных данных. В моем случае это упакованное изображение. Я хочу иметь хороший способ доступа к пикселям и их значениям. Так что я могу сделать что-то вроде этого:
void bar(int* data,size_t size)
{
Pixel2* img = reinterpret_cast<Pixel*>(data);
std::cout << "Pixel 13 has blue value: " << img[13].blue;
}
Я видел код, использующий это в cuda, и он работал, но я хочу знать, все ли в порядке, так как он, кажется, не охватывается тем, что я читал о POD. Я просто что-то пропустил в POD или это может что-то потерпеть неудачу?
Изменить: Есть ли разница между:
foo(access->x());
foo(access->data[0]);
Я думал, что второе должно быть допустимым, поскольку для POD-типов первая переменная-член имеет тот же адрес, что и объект?
Изменить: что я беру из ответов: это UB во всех случаях, которые я упомянул. Тогда можно было бы использовать итератор с произвольным доступом, который дает мне доступ, который я хотел бы получить.
2 ответа
Вызов нестатической функции-члена для несуществующего объекта и выполнение доступа к элементу класса для нестатического члена-данных для несуществующего объекта являются неопределенным поведением.
Ничто в вашем коде не создает Pixel
или же Pixel2
объект.
Я могу быть совершенно не прав, но мне кажется, что ваша структура на самом деле UB. Однако это вряд ли когда-либо случится.
(Невероятный) вариант использования, который может принести некоторый вред, - это когда выравнивание вашего вектора (которое может быть предоставлено, например, из библиотеки) и структуры различаются. Этого не должно быть, если код скомпилирован с тем же компилятором и теми же настройками, за исключением того, что вы сами его выравниваете. Рассмотрим выравнивание вектора по-разному, обе структуры приведут к UB. Или ваша структура выравнивания отличается, то же самое здесь, UB. Неопределенное поведение, однако, происходит не от выравнивания, а от того, что reinterpret_cast ничего не знает об этом.
В качестве быстрого и грязного примера:
struct Pixel2 {
alignas(8) int red;
alignas(8) int green;
alignas(8) int blue;
};
Даст вам неправильные значения для ваших пикселей. То же самое можно сделать со структурой, в которой вы используете массив int.
Посмотрите этот пример, где вы можете поиграть. Здесь обе структуры не могут получить правильные значения. Для варианта, в котором вектор выровнен по-другому, замените комментарии в строке 69/70 (замените std::vector<int> data;
с static_vector<int, 128> data;
).
Некоторые достойные упоминания ответы: