Строгое правило совмещения и смещение типов в C ++

Я пытаюсь понять неопределенное поведение при нарушении строгого правила псевдонима. Я прочитал много статей о SO, чтобы понять это. Однако остается один вопрос: я не очень понимаю, когда два типа нелегальных псевдонимов. cpp-reference утверждает:

Псевдоним типа

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

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

Я также нашел хороший пример на SO, где я ясно вижу проблему:

int foo( float *f, int *i ) { 
    *i = 1;               
    *f = 0.f;            

   return *i;
}

int main() {
    int x = 0;

    std::cout << x << "\n";   // Expect 0
    x = foo(reinterpret_cast<float*>(&x), &x);
    std::cout << x << "\n";   // Expect 0?
}

int и float не похожи друг на друга, и эта программа, возможно, сеет хаос. Чего я не вижу и не понимаю, так это следующей модификации:

struct A
{
    int a;
};

struct B
{
    int b;
};

A foo( A *a, B *b ) { 
    a->a = 1;               
    b->b = 0;            

    return *a;
}

int main() {
    A a;
    a.a = 0;


    std::cout << a.a << "\n";   // Expect 0
    a = foo(&a, reinterpret_cast<B*>(&a));
    std::cout << a.a << "\n";   // Expect 0?
}

Являются ли A и B похожими типами, и все в порядке, или они имеют незаконный псевдоним, и у меня неопределенное поведение. И если это законно, то потому ли, что A и B являются агрегатами (если да, что мне нужно изменить, чтобы сделать его неопределенным поведением)?

Будем очень признательны за любые предупреждения и помощь.

РЕДАКТИРОВАТЬ О проблеме дублирования

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


person user32434999    schedule 23.08.2018    source источник
comment
Возможный дубликат reinterpret_cast против строгого псевдонима   -  person Richard Critten    schedule 23.08.2018
comment
это незаконно. A и B - несвязанные типы.   -  person bolov    schedule 23.08.2018
comment
... Совместимы ли типы A и B ...: Нет   -  person Richard Critten    schedule 23.08.2018
comment
@RichardCritten Я не понимаю, почему вы отмечаете вопрос как повторяющийся, поскольку другой на самом деле не пытается прояснить, когда два типа совместимы, что является основной проблемой этого вопроса. Я снова не понимаю чего-то важного?   -  person user32434999    schedule 23.08.2018
comment
@RichardCritten Не могли бы вы объяснить свое «Нет» более четко?   -  person user32434999    schedule 23.08.2018


Ответы (3)


Нет, это незаконно, и у вас есть неопределенное поведение:

8.2.1 Категория значения [basic.lval]

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

(11.1) - динамический тип объекта,

(11.2) - cv-квалифицированная версия динамического типа объекта,

(11.3) - тип, подобный (как определено в 7.5) динамическому типу объекта,

(11.4) - тип, который представляет собой знаковый или беззнаковый тип, соответствующий динамическому типу объекта,

(11.5) - тип, который является типом со знаком или без знака, соответствующим cv-квалифицированной версии динамического типа объекта,

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

(11.7) - тип, который является (возможно, cv-квалифицированным) типом базового класса динамического типа объекта,

(11.8) - тип char, unsigned char или std :: byte


63) Назначение этого списка - указать те обстоятельства, при которых объект может иметь или не иметь псевдоним.

person bolov    schedule 23.08.2018

В выражении b->b = a; неопределенное поведение связано не с назначением, а с выражением доступа к члену класса, b->b. Если бы это выражение не было UB, ваш код не был бы UB.

В [expr.ref] / 1 указано, что Доступ к члену класса представляет собой доступ к объекту b (слева от - ›):

Постфиксное выражение, за которым следует точка. или стрелка - ›, за которой необязательно следует шаблон ключевого слова ([temp.names]), а затем идущее выражение, является постфиксным выражением. Выражается постфиксное выражение перед точкой или стрелкой; [67] результат этого вычисления вместе с id-выражением определяет результат всего постфиксного выражения.

[67] Если вычисляется выражение доступа к члену класса, оценка подвыражения происходит, даже если результат не нужен для определения значения всего постфиксного выражения, например, если id-выражение обозначает статический член.

жирный шрифт

Итак, b->b прочтите значение объекта a с выражением типа B, и здесь применяется правило, которое вы цитируете.

person Oliv    schedule 23.08.2018
comment
Учитывая такое утверждение, как intPtr1 = &someVolatileStructArray[i++].someIntMember;, как в Стандарте описывается, что делается с подвыражением someVolatileStructArray[i++]? Поскольку оператор i увеличивается, но не выполняет временный доступ, я думаю, ясно, что код делает что-то с этим подвыражением, но не оценивает его . - person supercat; 25.08.2018
comment
@supercat Если someVolatileStructArray[i++] Я не нахожу ничего ясного в стандарте. Я считаю, что правило в [exp.ref] ничего не говорит о доступе к члену виртуальной базы или виртуальной функции или имеет в данном случае для volatile. Итак, чтобы сделать язык единообразным, я полагаю, что в [exp.ref] / 1 оценка также означает доступ. - person Oliv; 26.08.2018

Что касается похожих типов, в разделе reinterpret_cast есть полезные объяснения и примеры:

Неформально, два типа похожи, если, игнорируя cv-квалификацию верхнего уровня:

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

Например:

  • const int * volatile * и int * * const похожи;
  • const int (* volatile S::* const)[20] и int (* const S::* volatile)[20] похожи;
  • int (* const *)(int *) и int (* volatile *)(int *) похожи;
  • int (S::*)() const и int (S::*)() не похожи;
  • int (*)(int *) и int (*)(const int *) не похожи;
  • const int (*)(int *) и int (*)(int *) не похожи;
  • int (*)(int * const) и int (*)(int *) похожи (они одного типа);
  • std::pair<int, int> и std::pair<const int, int> не похожи.

Это правило включает анализ псевдонимов на основе типов, в котором компилятор предполагает, что значение, считываемое через glvalue одного типа, не изменяется при записи в glvalue другого типа (с учетом исключений, указанных выше).

Обратите внимание, что многие компиляторы C ++ ослабляют это правило как расширение нестандартного языка, чтобы разрешить доступ неправильного типа через неактивный член объединения (такой доступ не определен в C

person bernie    schedule 18.08.2020