Можем ли мы получить доступ к члену несуществующего союза? - PullRequest
0 голосов
/ 05 ноября 2018

В стандарте 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 запрещает нам вызывать функцию-член объединения или агрегата «castted to»:

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

Учитывая, что статический доступ к элементу данных или функция статического члена могут напрямую выполняться с использованием квалифицированного имени (a_class::a_static_member), единственный вариант использования [basic.lval] /11.6, может состоять в доступе к члену объединения "castted to". Я думал об использовании этого последнего стандартного правила для реализации «оптимизированного варианта». Этот вариант может содержать либо объект класса 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 и Т2; поведение такое, как если бы соответствующий член T1 был назначен.

И [class.union] / 1 :

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

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

Ответы [ 2 ]

0 голосов
/ 06 ноября 2018

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

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

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


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

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

0 голосов
/ 06 ноября 2018

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

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

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

...