Можем ли мы получить доступ к члену несуществующего союза?

В стандарте c ++ в [basic.lval] /11.6 говорится:

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

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

Это предложение является частью правила строгого псевдонима.

Может ли это позволить нам получить доступ к неактивному члену несуществующего союза? Как в:

struct A{
  int id :1;
  int value :32;
  };
struct Id{
  int id :1;
  };

union X{
  A a;
  Id id_;
  };

void test(){
  A a;
  auto id = reinterpret_cast<X&>(a).id_; //UB or not?
  }

Примечание. Ниже поясняйте, чего я не понимаю в стандарте и почему приведенный выше пример может быть полезен.

Интересно, чем может быть полезен [basic.lval] /11.6.

[class.mfct.non-static] / 2 запрещает нам вызывать функцию-член объединения или агрегата, "приведенного к":

Если нестатическая функция-член класса X вызывается для объекта, который не относится к типу X или к типу, производному от X, поведение не определено.

Учитывая, что доступ к статическим элементам данных или статическая функция-член может выполняться напрямую с использованием квалифицированного имени (a_class::a_static_member), единственным полезным случаем использования [basic.lval] /11.6 может быть доступ член профсоюза "кастом". Я думал об использовании этого последнего стандартного правила для реализации «оптимизированного варианта». Этот вариант может содержать либо объект класса A, либо объект класса B, два из которых начинаются с битового поля размера 1, обозначающего тип:

class A{
  unsigned type_id_ :1;
  int value :31;
  public:
  A():type_id_{0}{}
  void bar{};
  void baz{};
  };

class B{
  unsigned type_id_ :1;
  int value :31;
  public:
  B():type_id_{1}{}
  int value() const;
  void value(int);
  void bar{};
  void baz{};
  };

struct type_id_t{
  unsigned type_id_ :1;
  };

struct AB_variant{
  union {
    A a;
    B b;
    type_id_t id;};
    //[...]
  static void foo(AB_variant& x){
    if (x.id.type_id_==0){
      reinterpret_cast<A&>(x).bar();
      reinterpret_cast<A&>(x).baz();
      }
    else if (x.id.type_id_==1){
      reinterpret_cast<B&>(x).bar();
      reinterpret_cast<B&>(x).baz();
      }
    }
 };

Вызов AB_variant::foo не вызывает неопределенного поведения, пока его аргумент ссылается на объект типа AB_variant, благодаря правилу взаимопреобразования указателя [basic.compound] / 4. Доступ к неактивному члену объединения type_id_ разрешен, поскольку id принадлежит общей начальной последовательности из A, B и type_id_t [class.mem] / 25:

Но что произойдет, если я попытаюсь вызвать его с полным объектом типа A?

A a{};
AB_variant::foo(reinterpret_cast<AB_variant&>(a));

Проблема здесь в том, что я пытаюсь получить доступ к неактивному члену несуществующего союза.

Два соответствующих стандартных абзаца: [class.mem] / 25:

В объединении стандартного макета с активным членом типа структуры T1 разрешено читать нестатический член данных m другого члена объединения типа структуры T2 при условии, что m является частью общей начальной последовательности T1 и T2; поведение такое, как если бы был назначен член-корреспондент T1.

И [class.union] / 1:

В объединении нестатический член данных активен, если его имя относится к объекту, время существования которого началось и не закончилось.

Q3. Означает ли выражение «его название», что «объект» на самом деле является объектом, созданным в рамках живого союза? Или он может ссылаться на объект a из-за [basic.lval] /11.6.


person Oliv    schedule 05.11.2018    source источник
comment
Q1, Q2 ... Q3 ... Разве это не слишком широкое определение SO?   -  person StoryTeller - Unslander Monica    schedule 05.11.2018
comment
@StoryTeller Я бы сказал, что это всего лишь структура вопроса.   -  person Quentin    schedule 05.11.2018
comment
К сожалению, это, вероятно, больше дискуссионный вопрос, чем вопрос, на который может быть конкретный ответ, поэтому я склонен согласиться: «слишком широко».   -  person Paul R    schedule 05.11.2018
comment
Первый пример кажется явным нарушением строгого псевдонима, потому что вы разыменовываете указатель на A, который на самом деле является указателем на int. (на самом деле это похоже на опечатку и должно быть reinterpret_cast<A*>(&j) и даже не компилируется как есть)   -  person user7860670    schedule 05.11.2018
comment
@Quentin - Если под структурой вы имеете в виду 3 разных вопроса. Вы честно говорите мне, что Q1 и Q2 не могут стоять сами по себе?   -  person StoryTeller - Unslander Monica    schedule 05.11.2018
comment
@StoryTeller Вопрос в заголовке - это прямой вопрос, на который можно дать два ответа: да или нет. Но я хочу также пояснения. Поэтому я задаю вопросы, которые показывают, что я не понимаю стандарт. Если вы ответите на эти вопросы, я получу пояснение к ответу на главный вопрос в заголовке.   -  person Oliv    schedule 05.11.2018
comment
Тогда, пожалуйста, ограничьте свой вопрос тем, что имеет непосредственное отношение к названию. Все остальное просто раздувание.   -  person StoryTeller - Unslander Monica    schedule 05.11.2018
comment
Хорошо, я поместил раздувание в Заметку.   -  person Oliv    schedule 05.11.2018
comment
Как насчет того, чтобы просто задать отдельные вопросы? Таким образом, у вас гораздо больше шансов получить должное внимание к каждому пункту.   -  person StoryTeller - Unslander Monica    schedule 05.11.2018
comment
@StoryTeller Я сделал новый вопрос и уменьшил его один. Стоит ли мне продолжать делиться?   -  person Oliv    schedule 05.11.2018
comment
@KamilCuk, я только что исправил эти ошибки копирования / вставки, спасибо. Второй пример - это некоторая форма реализации виртуальной функции вручную. Это строгое нарушение псевдонима? Это действительно вопрос. Строгое нарушение правила псевдонима происходит, когда мы обращаемся к значению объекта неправильного типа с исключениями, определенными в [basic.lval] /11.6. Этот примерный код попадает в это исключение, проблема в том, является ли доступ к члену класса или нет, где выражение объекта как неправильный тип - UB? Он явно указан буквально для вызова функции-члена, но не для доступа к нестатическим элементам данных.   -  person Oliv    schedule 05.11.2018
comment
@StoryTeller Q1 был безнадежно широким, если брать его отдельно, ИМО. Но это не тот холм, на котором я хочу умереть;)   -  person Quentin    schedule 05.11.2018


Ответы (2)


[expr.ref] /4.2 определяет, что E1.E2 означает, если E2 является нестатическим элементом данных. :

Если E2 является нестатическим членом данных [...], выражение обозначает именованный член объекта, обозначенного первым выражением.

Это определяет поведение только для случая, когда первое выражение фактически обозначает объект. Поскольку в вашем примере первое выражение не обозначает никакого объекта, поведение не определено по пропуску; см. [defns.undefined] («Неопределенное поведение может ожидаться, если в этом документе отсутствует какое-либо явное определение поведения ... ").


Вы также неправильно понимаете, что означает «доступ» в правиле строгого псевдонима. Это означает «прочитать или изменить значение объекта» ([defns.access]). Выражение доступа к члену класса, именующее нестатический член данных, не считывает и не изменяет значение любого объекта и, следовательно, не является «доступом», и поэтому никогда не бывает «доступа ... через» значение glvalue «агрегат или объединение». type "по причине выражения доступа к члену класса.

[basic.lval] /11.6 по сути скопирован из C, где он на самом деле что-то означал, потому что присвоение или копирование struct или union обращается к объекту в целом. В C ++ это бессмысленно, потому что присвоение и копирование типов классов выполняется с помощью специальных функций-членов, которые либо выполняют поэлементное копирование (и, таким образом, «получают доступ» к членам индивидуально), либо работают с представлением объекта. См. основную проблему 2051.

person T.C.    schedule 06.11.2018
comment
Возможно, этот вопрос потерял последовательность, потому что мне пришлось его разбить. Фактически первая часть - здесь. Вы увидите, что я упоминаю, что такое доступ. Моя проблема заключается в таких выражениях, как «обозначить» или «имя» и т. Д. Должен ли я рассматривать имя как идентичность объекта (идентичность сущности)? В этом случае имя обозначает и может обозначать только подчиненный объект члена и никогда не объект, который может оказаться в том же месте? - person Oliv; 06.11.2018
comment
Таким образом, эта основная проблема также является ответом на другой вопрос, в чем [basic.lval] /11.6 может быть полезен? = ›Ничего не отвечайте на c ++? - person Oliv; 06.11.2018
comment
Я бы сказал, что предполагаемое значение правил псевдонима в C и производных правил в C ++ заключалось в том, что доступ, сделанный через lvalue доступа к члену, или указатель, который только что получен из него, должен обрабатываться (для целей псевдонима rules), как если бы это был доступ через родительское lvalue. Таким образом, доступ к lvalue, только что полученному из объединения lvalue, будет доступом через это объединение lvalue, которому, в свою очередь, будет разрешен доступ ко всем остальным членам объединения. Таким образом, для разрешения такого доступа необходимо правило родительского lvalue может получить доступ к дочерним элементам. - person supercat; 06.11.2018
comment
@supercat Итак, это правило должно быть таким же асимметричным, как и член правила. Что я делаю в своем примере кода, я использую его не в намеченном направлении, не так ли? - person Oliv; 07.11.2018
comment
@supercat Код, аналогичный OP, был поднят в отражателе CWG, и этот ответ согласуется с приведенным там ответом, поэтому я считаю, что он точно отражает текущую интерпретацию стандартной формулировки разработчиками компилятора C ++. - person T.C.; 07.11.2018
comment
@ T.C .: Стандарты намеренно предоставляют реализациям, предназначенным для специальных целей, широкое разрешение на поведение, которое сделает их непригодными для большинства других целей. Поведение gcc / clang на самом деле не интерпретация Стандарта, а скорее решение обработать диалект, который не подходит для большинства целей, для которых был изобретен C, и чья руководящая философия противоречит описанному Духу C. в Обосновании стандарта C. - person supercat; 07.11.2018

Есть много ситуаций, особенно связанных с выделением типов и объединениями, где одна часть стандарта C или C ++ описывает поведение некоторого действия, другая часть описывает перекрывающийся класс действий как вызов UB, а область перекрытия включает некоторые действия, которые должны обрабатываться последовательно всеми реализациями, а также другими реализациями, которые было бы непрактично поддерживать хотя бы в некоторых реализациях. Вместо того, чтобы пытаться полностью описать все случаи, которые следует рассматривать в соответствии с определением, авторы Стандарта ожидали, что реализации будут стремиться поддерживать Дух C, описанный в Обосновании, включая принцип «Не мешайте программисту делать то, что нужно». должно быть сделано ". Это, как правило, приводит к качественным реализациям, отдавая приоритет определению поведения, когда это необходимо для удовлетворения потребностей их клиентов, в то же время отдавая приоритет «неопределенности» поведения, когда это позволяет проводить оптимизацию, которая также обслуживает потребности клиентов.

Единственный способ рассматривать Стандарт C или C ++ как определение полезного языка - это распознать категорию действий, поведение которых описывается одной частью Стандарта и классифицироваться как UB другой, и признавать обработку действий в этой категории как Вопрос качества реализации выходит за рамки юрисдикции Стандарта. Авторы Стандарта ожидали, что авторы компилятора будут чуткими к потребностям своих клиентов, и поэтому не рассматривали конфликты между поведенческими определениями и неопределениями как особую проблему. Таким образом, они не видели необходимости определять такие термины, как «объект», «lvalue», «время жизни» и «доступ» способами, которые можно было бы применять последовательно, не создавая таких конфликтов, и поэтому созданные ими определения не могут использоваться для целей принятия решений. следует ли определять конкретные действия при наличии таких конфликтов.

Следовательно, до тех пор, пока Стандарты не признают больше концепций, связанных с объектами и способами доступа к ним, вопрос о том, должна ли качественная реализация, предназначенная для того, чтобы быть подходящей для какой-либо цели, поддерживать определенное действие, будет зависеть от того, следует ли ожидать от ее авторов. признать, что действие было бы полезно для этой цели.

person supercat    schedule 06.11.2018