Четко ли определен этот тип каламбура?

Читая цитату в этом ответе о строгом правиле псевдонимов, я вижу следующее для C ++ 11:

Если программа пытается получить доступ к сохраненному значению объекта через glvalue, отличный от одного из следующих типов, поведение не определено:

  • ...

  • тип агрегата или объединения, который включает один из вышеупомянутых типов среди своих элементов или нестатических элементов данных (включая, рекурсивно, элемент или нестатический член данных субагрегата или содержащегося объединения),

  • ...

Поэтому я понимаю, что следующий код не нарушает строгого правила псевдонима:

#include <iostream>
#include <cstdint>
#include <climits>
#include <limits>

struct PunnerToUInt32
{
    std::uint32_t ui32;
    float fl;
};

int main()
{
    static_assert(std::numeric_limits<float>::is_iec559 &&
                  sizeof(float)==4 && CHAR_BIT==8,"Oops");
    float x;
    std::uint32_t* p_x_as_uint32=&reinterpret_cast<PunnerToUInt32*>(&x)->ui32;
    *p_x_as_uint32=5;
    std::cout << x << "\n";
}

Итак, строгое правило псевдонима выполнено. По какой-либо другой причине это все еще проявляет неопределенное поведение?


person Ruslan    schedule 23.06.2016    source источник
comment
как соблюдается правило строгого псевдонима? ни один из типов не является char *, это единственный тип, который получает бесплатный проход. \   -  person Richard Hodges    schedule 23.06.2016
comment
@RichardHodges. Ты меня опередил.   -  person Jonathan Mee    schedule 23.06.2016
comment
@JonathanMee, некоторые ошибки просто не могут остаться незамеченными. Ты получаешь от меня +1   -  person Richard Hodges    schedule 23.06.2016
comment
Практическое правило: если вы видите reinterpret_cast, а это не char*, вы наблюдаете строгое нарушение псевдонима в 99,9% случаев (0,1% зарезервировано для случаев, когда указатель фактически не разыменовывается, а используется для другого приведения обратно к то, что разрешено).   -  person SergeyA    schedule 23.06.2016


Ответы (2)


Вы не можете этого сделать: &reinterpret_cast<PunnerToUInt32*>(&x)

В правилах reinterpret_cast говорится:

Когда указатель или ссылка на объект, динамический тип которого DynamicType, reinterpret_cast (или приведение в стиле C) к указателю или ссылке на объект другого типа AliasedType, приведение всегда выполняется успешно, но полученный указатель или ссылка могут использоваться только для получить доступ к объекту, если выполнено одно из следующих условий:

  • AliasedType (возможно, квалифицировано по резюме) DynamicType
  • AliasedType и DynamicType оба (возможно, многоуровневые, возможно, квалифицированные на каждом уровне) указатели на один и тот же тип T
  • AliasedType является (возможно, cv-квалифицированным) подписанным или беззнаковым вариантом DynamicType
  • AliasedType - это агрегированный тип или тип объединения, который содержит один из вышеупомянутых типов в качестве элемента или нестатического члена (включая, рекурсивно, элементы субагрегатов и нестатические элементы данных содержащихся объединений): это делает безопасным получение полезный указатель на структуру или объединение, заданный указателем на ее нестатический член или элемент.
  • AliasedType является (возможно, квалифицированным cv) базовым классом DynamicType
  • AliasedType равно char или unsigned char: это позволяет исследовать объектное представление любого объекта в виде массива unsigned char

Поскольку ничто из этого не верно для комбинации DynamicType равно float и AliasedType равно PunnerToUInt32, указатель не может использоваться для доступа к объекту, что вы делаете. Делаем поведение неопределенным.

Для получения дополнительной информации см .: Почему reinterpret_cast не принудительно копирует copy_n для преобразований между типами одинакового размера?

РЕДАКТИРОВАТЬ:

Разбивая на части 4 th bullet int bite size, получаем:

  1. "AliasedType"
    Здесь принято PunnerToUInt32
  2. "является агрегатным типом или типом объединения"
    PunnerToUInt32 соответствует требованиям, поскольку он отвечает требованиям тип агрегата:

    • array type
    • class type (typically, struct or union), that has
      • no private or protected non-static data members
      • никаких конструкторов, предоставляемых пользователем, в том числе унаследованных от общедоступных баз (разрешены явно заданные по умолчанию или удаленные конструкторы)
      • нет виртуальных, частных или защищенных базовых классов
      • нет виртуальных функций-членов
  3. "который содержит один из вышеупомянутых типов в качестве элемента или нестатического члена (включая, рекурсивно, элементы субагрегатов и нестатические элементы данных содержащихся объединений)"
    И снова PunnerToUInt32 подходит, потому что он float fl член

  4. "это делает безопасным получение полезного указателя на структуру или объединение"
    Это последняя правильная часть, поскольку AliassedType - это PunnerToUInt32
  5. "указан указатель на его нестатический член или элемент"
    Это нарушение, потому что DynamicType, который равен x, не является членом PunnerToUInt32

Из-за нарушения части 5 работа с этим указателем имеет неопределенное поведение.

Если вас интересуют некоторые рекомендуемые материалы, вы можете проверить Оптимизацию пустой базы, если нет Я дам вам здесь первостепенное значение:

Для StandardLayoutTypes требуется пустая базовая оптимизация, чтобы поддерживать требование, чтобы указатель на объект стандартного макета, преобразованный с использованием reinterpret_cast, указывал на его начальный член.

Таким образом, вы можете использовать пулю 4 th reinterpret_cast, выполнив следующие действия:

PunnerToUInt32 x = {13, 42.0F};
auto y = reinterpret_cast<PunnerToUInt32*>(&x.ui32);

Живой пример

person Jonathan Mee    schedule 23.06.2016
comment
Но не является ли AliasedType=PunnedToUInt32 агрегатным типом, который содержит DynamicType=float как нестатический член, как описано в пункте 4, тем самым устраняя нарушение? - person Ruslan; 24.06.2016
comment
@Ruslan Мне жаль, что я так плохо поработал, отвечая на твой вопрос. Я просто не понял, о чем вы спрашиваете, до вашего комментария. Я редактировал, и, надеюсь, вы найдете это ясным. Если нет, дайте мне знать, как я могу объяснить. - person Jonathan Mee; 24.06.2016
comment
Вы проверяли действующий стандарт, а не только cppreference.com? По крайней мере, в моей копии стандарта C ++ 14 это делает безопасным получение пригодного для использования указателя на структуру или объединение с учетом указателя на ее нестатический член или элемент. часть отсутствует. Смотрю в стандарте 3.10.10. - person khuttun; 01.09.2017
comment
@khuttun Не могли бы вы дать мне название раздела по этому поводу. Моя версия стандарта 3.10 - [defns.dynamic.type.prvalue]. Однако из-за проблемы с псевдонимом типов я уже знаю, что вам не будет разрешено reinterpret_cast к несвязанному типу. - person Jonathan Mee; 01.09.2017
comment
3.10. Значения L и r. Текст запускается Если программа пытается получить доступ к сохраненному значению объекта ... - person khuttun; 01.09.2017
comment
@khuttun А, я понимаю, что вы говорите, вы говорите, что en.cppreference.com добавил этот текст. У них действительно есть разъяснение. Вы говорите о разделе 6.10 [basic.lval] 8.6. - person Jonathan Mee; 01.09.2017
comment
Да, это то, что я говорил. Итак, я хотел сказать, что чтение по стандарту и чтение с en.cppreference.com дали бы другой ответ на этот вопрос. - person khuttun; 02.09.2017
comment
Я начал обсуждение этого также в cppreference: en.cppreference.com/w/ Обсуждение: cpp / language / reinterpret_cast - person khuttun; 02.09.2017
comment
@khuttun Спасибо за действительно полезный комментарий. Я слежу за обсуждением и сообщу, если мы что-нибудь доработаем. А пока получите +1 за случайный ответ в качестве благодарности. - person Jonathan Mee; 05.09.2017
comment
Я только что подтолкнул к полному переписыванию обсуждения строгого псевдонима cppreference вчера, к вашему сведению. - person T.C.; 01.10.2017

Если бы p_x_as_uint32 каким-то образом указывал на x 1, тогда *p_x_as_uint32=5 обращался бы к объекту типа float через glvalue типа uint32_t, что привело бы к неопределенному поведению.

Речь идет о «доступе», и все, что имеет значение, - это тип используемого glvalue (uint32_t) и фактический тип объекта, к которому осуществляется доступ (float). Мучительная серия забросов, использованная для получения указателя, не имеет значения.

Стоит помнить, что существует строгое правило псевдонимов, позволяющее анализировать псевдонимы на основе типов. Каким бы мучительным ни был выбранный маршрут, если вы можете юридически "создать ситуация, когда int* и float* могут существовать одновременно, и оба могут использоваться для загрузки или хранения одной и той же памяти, вы уничтожаете TBAA ". Если вы думаете, что формулировка стандарта каким-то образом позволяет вам это сделать, вы, вероятно, ошибаетесь, но если вы были правы, то все, что вы обнаружили, - это дефект в стандартной формулировке.


1 Доступ к члену класса является неопределенным (по пропуску), потому что нет фактического объекта PunnerToUInt32. Однако доступ члена класса не является "доступом" в значении строгое правило алиасинга; последнее означает «читать или изменять значение объекта».

person T.C.    schedule 01.09.2017
comment
Имеются три варианта: (1) Запретить компиляторам предполагать, что псевдонимы не будут происходить, даже если нет никаких доказательств того, что это могло бы быть; (2) Разрешить компиляторам предполагать, что псевдонимы не будут возникать только в тех местах, где нет никаких доказательств того, что это могло бы быть; (3) Поощряйте компиляторы предполагать, что наложения имен не произойдет, даже если есть веские доказательства того, что они есть. Что имело бы наибольший смысл? Я бы предположил, что единственная причина, по которой правила когда-либо были одобрены, состоит в том, что люди ожидали, что авторы компиляторов признают, что №3 был настолько ослепительно глупым, что они интерпретировали бы правила просто как разрешающие №2. - person supercat; 30.09.2017
comment
Если имелось в виду значение # 3, и если бы авторы не хотели намеренно сделать язык менее мощным, чем он был бы без правил, они бы добавили директивы, достаточные для того, чтобы позволить программистам делать все, что они могут делать без правила (например, вариант размещения new, который переинтерпретирует любые битовые комбинации, которые хранятся в указанном хранилище). Компиляторы могут разрешить полезную переинтерпретацию указателя без необходимости блокировать все оптимизации, если они распознают переинтерпретацию указателя как свидетельство алиасинга в непосредственной близости от него. Я не понимаю, почему это так сложно. - person supercat; 30.09.2017
comment
В качестве простого принципа: агрессивно оптимизируйте там, где код не делает ничего странного, но будьте очень осторожны, делая очень мало предположений, когда код делает странные вещи. Оптимизатор, который проявляет осторожность в местах, где код делает странные вещи, может безопасно быть более агрессивным там, где это не так, чем тот, который игнорирует свидетельства странностей. - person supercat; 30.09.2017
comment
Если у вас есть проблемы с правилами в стандарте, вам следует написать документ WG21, желательно с более точной спецификацией того, когда разрешено использование псевдонимов, чем это странно. Публикация длинных ... комментариев ... к ответам SO, которые объясняют, каковы текущие правила, ничего не меняет. - person T.C.; 01.10.2017