Позволит ли P1839 получить доступ к подсостояниям по смещениям в представлениях объектов?

Вопрос или проблема

Это в определенном смысле является продолжением Является ли это неопределенным поведением – доступ к подобъекту путем добавления смещения байтов к адресу охватывающего объекта? при предположении, что 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 и других связанных документах.

Оцените материал
Добавить комментарий

Капча загружается...