Вопрос или проблема
Это в определенном смысле является продолжением Является ли это неопределенным поведением – доступ к подобъекту путем добавления смещения байтов к адресу охватывающего объекта? при предположении, что P1839 будет принят.
Рассмотрим следующий код:
struct V2
{
float x;
float y;
float& f()
{
// Если это важно, замените char* или unsigned char* на std::byte
std::byte* self = reinterpret_cast<std::byte*>(this); // (1)
std::byte* rep_y = self + offsetof(V2, y); // (2)
float* ptr_y = reinterpret_cast<float*>(rep_y); // (3)
return *ptr_y; // (4)
}
};
Предыдущие обсуждения о том, является ли подобный код допустимым, сосредоточились на последствиях проверки представлений объектов после C++17, где мнение, похоже, заключается в том, что формально арифметика указателей в (2) является неопределенным поведением из-за того, как специфицируются представления объектов.
Устранение итерации по представлению объекта, похоже, является основной мотивацией P1839, так что (2) больше не является неопределенным поведением. Это требует введения формулировок, касающихся вложенности и подобъектов, и именно в этот момент я понимаю, что недостаточно хорошо разбираюсь в стандарте/терминологии, чтобы прийти к выводу, является ли f
, как она реализована выше, допустимой.
Я осведомлён о том, что P1839 включает следующую сноску, но кажется, что она говорит о случаях, когда смещения применяются вне текущих ограничений досягаемости, что не так в данном случае?
Эти ограничения, основанные на досягаемости, ограничивают совместимость между C и C++, особенно когда речь идет о коде на C, который использует offsetof для реализации инвазивных структур данных. Ведется подготовка отдельной работы, которая предлагает убрать эти ограничения. Эти направления создают дополнительные сложности в спецификации, которые здесь не будут обсуждаться.
Во-первых, в случае, когда смещение не применяется, правильно ли я говорю, что именно точка “один является представлением объекта другого или первым элементом этого” позволяет выполнять переопределяющее приведение между T*
объекта this
, его представлением объекта в виде (скажем) std::byte*
, а затем назад к T*
(с доступом к значению)?
Таким образом, может ли указатель rep_y
, полученный через (2), интерпретироваться как первый элемент представления объекта y
и, следовательно, разрешать доступ к значению y
через подходящим образом приведенный указатель (3)? Основываясь на вышесказанном, это было бы возможно, если бы было первоначально получено через &y
, но действительно ли это все еще верно, когда оно сформировано из представления объекта V2
? Почему/почему нет?
Если это технически не (первый элемент) представления объекта y
, или если есть какой-то другой ключевой момент, который я упустил, подразумевают ли изменения в определении std::launder
, что последующая стирка предоставит правильный результат? В общем, похоже, что это указывает на объект float, досягаемый из первоначального указателя, так что будет ли это работать? Есть ли какие-либо осложнения из-за того, что мы сконструировали указатель на (часть) представления объекта, а не указатель на предполагаемый объект (или под-объект)?
Наконец, если ничего из этого не верно, есть ли код, подобный этому, который либо является допустимым, либо предполагается быть разрешенным (в подобном духе к циклу проверки представления объекта P1839)?
Ответ или решение
Вопрос о том, позволят ли изменения, предложенные в P1839, доступ к подобъектам по смещениям в представлениях объектов, действительно является важным аспектом понимания безопасности и законности доступа к памяти в языке C++.
Проблема с доступом к подобъектам
В представленной программе у нас есть структура V2
, в которой содержатся два значения типа float
. Метод f()
пытается получить доступ к полю y
, используя указатель на объект в виде байтового указателя. Основная часть кода, представляющая собой доступ к полю y
, заключается в использовании смещения, вычисленного с помощью offsetof
. Важно отметить, что до принятия P1839, подобное манипулирование считалось неопределенным поведением (UB) из-за строгих правил доступа к репрезентации объектов после C++17.
Влияние P1839 на правила доступа
P1839 предлагает упрощение правил, касающихся доступа к представлению объектов и структур данных в C++, что делает выполнение операций, подобных тем, что в коде выше, менее рискованным. Основная цель P1839 заключается в устранении ограничений, связанных с охватом объектов и подструктур. Однако, несмотря на предполагаемое снятие некоторых ограничений, остаются вопросы об интерпретации указателей и их доступности.
Доступ к подножкопным элементам
В контексте вашего вопроса о rep_y
и его интерпретации как указателя на объект y
, необходимо рассмотреть, доступны ли подобные операции после предполагаемых изменений. В C++28 (или в версии, которая примет P1839), указанные изменения означают, что указатель rep_y
действительно может быть интерпретирован как первый элемент репрезентации y
, что позволит выполнять операции над значением y
по полученному указателю. Таким образом, можно предположить, что вызов *ptr_y
будет действовать корректно.
Однако важно уточнить, что при работе с указателями, созданными в результате манипуляций с объектом, следует учитывать концепцию "достижимости". Если y
все еще является легитимным подобъектом V2
, то указатель ptr_y
будет корректным после процедурной "лаундеризации" с использованием std::launder
, что позволит избежать появления потенциального UB, если условие "достижимости" выполнено.
Указание на "достижимость"
С учётом этого, если y
доступен для объектоориентированного анализа и доступ к нему по смещениям в контексте P1839 поддерживается, то нет необходимости в дополнительных манипуляциях или изменениях для доступа к его значению. Важно использовать std::launder
, чтобы убедиться, что доступ происходит к активному объекту подструктуры, что избавляет от возможностей UB.
Итоги и рекомендации
Если данная практика все еще вызывает сомнения, стоит рассмотреть более безопасные альтернативы, которые все еще позволяют получить доступ к подструктуре, в то время как соблюдаются принципы языка C++. Например, использование методов доступа, предоставляемых самими структурами, может сделать код более ясным и свести к минимуму риск возникновения неопределенного поведения.
Нет однозначного ответа на вопрос о "законности" таких операций до окончательного принятия P1839 и его интерпретации в стандарте, но измененные условия в P1839 должны способствовать более безопасному доступу, что должно быть вашим основным направлением при работе с C++.
При будущих обсуждениях таких практик, убедитесь, что вы постоянно проверяете обновления спецификаций языка и возможные улучшения, предложенные в P1839 и других связанных документах.