Может ли тип, который является членом союза, псевдоним этого союза? - PullRequest
0 голосов
/ 04 февраля 2019

По запросу на этот вопрос :

Стандарт C11 гласит, что указатель на объединение можно преобразовать в указатель на каждый из его членов.Из раздела 6.7.2.1p17:

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

Это означает, что вы можете сделать следующее:

union u {
    int a;
    double b;
};

union u myunion;
int *i = (int *)&u;
double *d = (double *)&u;

u.a = 2;
printf("*i=%d\n", *i);
u.b = 3.5;
printf("*d=%f\n", *d);

Но как же наоборот: в случае вышеприведенного объединения можно int * или double * безопасно преобразовать вunion u *?Рассмотрим следующий код:

#include <stdio.h>

union u {
    int a;
    double b;
};

void f(int isint, union u *p)
{
    if (isint) {
        printf("int value=%d\n", p->a);
    } else {
        printf("double value=%f\n", p->b);
    }
}

int main()
{
    int a = 3;
    double b = 8.25;
    f(1, (union u *)&a);
    f(0, (union u *)&b);
    return 0;
}

В этом примере указатели на int и double, оба из которых являются членами union u, передаются в функцию, где ожидается union u *,В функцию передается флаг, сообщающий ей, к какому «члену» обращаться.

Предполагается, что, как и в этом случае, доступ к члену соответствует типу объекта, который фактически был передан, является приведенным выше кодомLegal?

Я скомпилировал это на gcc 6.3.0 с -O0 и -O3, и оба дали ожидаемый результат:

int value=3
double value=8.250000

Ответы [ 4 ]

0 голосов
/ 05 февраля 2019

Стандарт не дает общего разрешения на доступ к объекту struct или union, используя lvalue типа члена, и, насколько я могу судить, не дает какого-либо конкретного разрешения на выполнение такого доступа, если толькочлен случается типа персонажа.Он также не определяет никаких средств, с помощью которых акт превращения int* в union u* может создать тот, который еще не существовал.Вместо этого создание любого хранилища, к которому когда-либо будет осуществляться доступ как union u, подразумевает одновременное создание union u объекта в этом хранилище.

Вместо этого Стандарт (ссылки, цитируемые в проекте C11 N1570)опирается на реализации для применения сноски 88 ( Цель этого списка - указать те обстоятельства, при которых объект может или не может иметь псевдоним. ) и признать, что "правило строгого наложения имен" (6.5p7)следует применять только тогда, когда на объект ссылаются как через lvalue его собственного типа, так и кажущееся несвязанным lvalue другого типа во время некоторого конкретного выполнения функции или цикла [т.е. когда объект псевдоним некоторого другого lvalue].

Вопрос о том, когда два значения могут рассматриваться как «кажущиеся не связанными» и когда реализация должна распознавать взаимосвязь между ними, является проблемой качества реализации.Clang и gcc, кажется, признают, что l-значения с формами unionPtr->value и unionPtr->value[index] связаны с *unionPtr, но, похоже, не могут распознать, что указатели на такие l-значения имеют какое-либо отношение к unionPtr.Таким образом, они распознают, что и unionPtr->array1[i], и unionPtr->array2[j] обращаются *unionPtr (поскольку подписка на массив через [], похоже, трактуется иначе, чем затухание массива в указатель), но не распознает, что *(unionPtr->array1+i) и *(unionPtr->array2+j) сделать аналогично.

Приложение - стандартная ссылка:

Учитывая

union foo {int x;} foo,bar;
void test(void)
{
  foo=bar;   // 1
  foo.x = 2; // 2
  bar=foo;   // 3
}

Стандарт будет описывать тип foo.x как int.Если второй оператор не имеет доступа к сохраненному значению foo, то третий оператор не будет иметь никакого эффекта.Таким образом, второй оператор обращается к сохраненному значению объекта типа union foo, используя lvalue типа int.Глядя на N1570 6.5p7:

Доступ к сохраненному значению объекта должен осуществляться только через выражение lvalue, которое имеет один из следующих типов: (сноска 88)

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

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

Обратите внимание, что для разрешенияЧтобы получить объект типа union foo, используйте lvalue типа int.Поскольку вышеприведенное является ограничением, любое его нарушение вызывает UB , даже если поведение конструкции в противном случае было бы определено стандартом .

.
0 голосов
/ 04 февраля 2019

Нет.Формально это не правильно.

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

Объединение резервирует пространство памяти для хранения наибольших его элементов:

Размер объединениядостаточно, чтобы вместить самый большой из его членов.

На обратной стороне места не может быть достаточно.

Учтите:

union
{
    char a;
    int b;
    double c;
} myunion;
char c;
((union myunion *)&c)->b = 0;

Создаст памятькоррупция.

Значение стандартного определения:

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

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

0 голосов
/ 04 февраля 2019

В этом примере указатели на int и double, оба из которых являются членами union u, передаются функции, в которой ожидается union u *.В функцию передается флаг, сообщающий ей, к какому «члену» обращаться.

Предполагается, что, как и в этом случае, доступ к члену соответствует типу объекта, который фактически был передан, является приведенным выше кодом.законно?

Вы, кажется, концентрируете свой анализ в отношении строгого правила алиасинга на типах членов профсоюза.Однако, учитывая

union a_union {
    int member;
    // ...
} my_union, *my_union_pointer;

, я склонен утверждать, что выражения вида my_union.member и my_union_pointer->member выражают доступ к сохраненному значению объекта типа union a_union в дополнение к доступу к объектутипа члена.Таким образом, если my_union_pointer на самом деле не указывает на объект, эффективный тип которого равен union a_union, то действительно существует нарушение строгого правила псевдонимов - относительно типа union a_union - и поэтому поведение не определено.

0 голосов
/ 04 февраля 2019

Что касается строгого псевдонима, нет проблемы перехода от указателя к типу (например, &a) к указателю на объединение, содержащее этот тип.Это одно из исключений из правила строгого алиасинга, C17 6.5 / 7:

Объект должен иметь свое сохраненное значение, доступное только через выражение lvalue, которое имеет один из следующих типов:
- тип, совместимый с действующим типом объекта, / - /
- агрегатный или объединенный тип, который включает один из вышеупомянутых типов среди своих членов

Так что это нормальнокак строгое псевдонимы, пока union содержит int / double.И преобразование указателя само по себе тоже хорошо определено.

Проблема возникает, когда вы пытаетесь получить доступ к содержимому, например к содержимому int как к большему double.Вероятно, это UB по нескольким причинам - я могу вспомнить хотя бы C17 6.3.2.3/7:

Указатель на тип объекта может быть преобразован в указатель на другой тип объекта.Если полученный указатель неправильно выровнен 69) для ссылочного типа, поведение не определено.

Там, где ненормативная нога-нота предоставляет дополнительную информацию:

69) В общем, понятие «правильно выровненный» является переходным: если указатель на тип A правильно выровнен для указателя на тип B, который, в свою очередь, правильно выровнен для указателядля типа C указатель на тип A корректно выравнивается для указателя на тип C.

...