#c #c 20
Вопрос:
struct header
{
int a1;
int a2;
// ...;
std::byte * get_data_bytes()
{
return align_up<data>( // make sure alignment requirements are met
reinterpret_cast<std::byte *>(this) sizeof(*this));
// maybe std::launder around the reinterpret_cast (only) is needed?
}
data amp; get_data()
{
return *std::launder(reinterpret_cast<data *>(get_data_bytes()));
}
void use_data()
{
get_data().use();
}
};
void example()
{
alignas(header) std::byte storage[/* plenty of space*/];
auto h = new (storage) header;
new (h->get_data_bytes()) data;
h->use_data(); // Does this eventually cause a UB?
}
Возможно ли это без UB? Если нет, то есть ли альтернатива?
Требование состоит в том, чтобы данные не были подобъектом заголовка, и не было
указателя/ссылки на данные из заголовка, чтобы избежать дополнительной
косвенности. Возможно, это было бы возможно с гибким пустым массивом, но я
не думаю, что это входит в стандарт.
Комментарии:
1. Не могли бы вы уточнить немного больше о требованиях к типу
data
? Я думаю, что вы танцуете вокруг нарушений псевдонимов здесь.2. @AndyG Я намеренно не предоставил никаких подробностей
data
, но можно начать обсуждение с простого тривиального типа с несколькими целыми числами внутри.3. Это называется «выстрелить себе в ногу». Возьмите начальный адрес структуры, добавьте смещение элемента, к которому вы хотите получить доступ, используйте приведение в стиле C, чтобы привести адрес (указатель) так, как вы хотите. Совершенно неопределенное поведение. Помните, что вам, возможно, придется учитывать отступы при расчете смещения.
4. @CppNerd13373 AFAIK нет, проблема в том, что в общем смысле вы пытаетесь взломать структуру. Проблема в том, что компилятор может поместить в массив больше заполнений, чем на самом деле требуется объекту
sizeof
. Это означает, что, даже если объект имеет asizeof 12
, он может быть выровнен по 16 и т. Д. На нем есть видео CppCon, если вам интересно5. @ThomasMatthews Несмотря на то, что этот пример выглядит так, как будто кто-то хотел бы выстрелить себе в ногу, почти везде вам приходится делать подобные вещи при общении с низкоуровневым кодом или оборудованием. Он очень часто представляет собой структуру с пустым массивом в конце, чтобы иметь возможность доступа к произвольным данным после объекта заголовка. Если вы уверены, что это неопределенное поведение, пожалуйста, не могли бы вы объяснить, почему и предоставить альтернативу?
Ответ №1:
Единственное, что даже гипотетически верно, — это является ли reinterpret_cast<std::byte *>(this)
указатель на один из байтов в массиве или нет. Но вы можете std::launder
это сделать, чтобы убедиться, что это так. Действительно, отмывание указателей байтов на/с достаточно полезно, чтобы вы могли создавать для них шаблонные функции:
template<typename T>
std::byte *to_byte_ptr(T *ptr)
{
return std::launder(reinterpret_cast<std::byte*>(ptr));
}
template<typename T>
T *from_byte_ptr(std::byte *ptr)
{
return std::launder(reinterpret_cast<T*>(ptr));
}
Все остальное в полном порядке. Вы создали header
хранилище, предоставляемое массивом байтов, поэтому там есть std::byte
объекты, доступные через launder
. И поскольку эти std::byte
объекты находятся в массиве, вы можете выполнять арифметику указателей на них. И хотя эти std::byte
объекты на самом деле не указывают на объекты, для которых они предоставляют хранилище, если у вас есть указатель с тем же адресом, что и нужный объект, вы можете launder
извлечь указатель на этот объект.
Комментарии:
1. Это именно то, о чем я думал, так что спасибо, что дали мне это представление. Как бы интуитивно это ни звучало, я не смог найти в стандарте уверенности, которая скажет мне, что отмывание этого или этого 1 после отмывания действительно указывает на элемент массива массива std::байт. Кроме того, при чтении требований к стирке я был озадачен требованиями к достижимости. Не могли бы вы провести меня, может быть, через это?