Может ли структура иметь собственный собственный и единственный член? - программирование
Подтвердить что ты не робот

Может ли структура иметь собственный собственный и единственный член?

Например, действительно ли этот код действителен или вызывает поведение undefined, нарушая правила псевдонимов?

int x;
struct s { int i; } y;
x = 1;
y = *(struct s *)&x;
printf("%d\n", y.i);

Я заинтересован в использовании техники, основанной на этом, для разработки переносного метода для выполнения псевдонимов.

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

static inline uint32_t read32(const unsigned char *p)
{
    struct a { char r[4]; };
    union b { struct a r; uint32_t x; } tmp;
    tmp.r = *(struct a *)p;
    return tmp.x;
}

GCC, по желанию, компилирует это на одну 32-разрядную нагрузку и, похоже, избегает проблем с псевдонимом, которые могут произойти, если p фактически указывает на тип, отличный от char. Другими словами, он действует как переносная замена атрибута GNU C __attribute__((__may_alias__)). Но я не уверен, действительно ли он определен...

4b9b3361

Ответ 1

Я считаю, что это все равно будет нарушать эффективные правила набора текста. Вы хотите получить доступ к ячейке памяти, которая не была объявлена ​​явно (или неявно через хранилище в случае динамического выделения), содержащую struct a через выражение этого типа.

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

Однако я считаю, что есть решение вашей проблемы: используйте __builtin_memcpy(), который доступен даже в автономных средах (см. ручная запись на -fno-builtin).


Обратите внимание, что проблема немного менее ясна, чем звук. Раздел C11 6.5 §7 говорит нам, что он имеет прекрасный доступ к объекту посредством выражения lvalue, которое имеет тип агрегата или объединения, который включает один из вышеупомянутых типов среди его членов.

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

Я считаю, что возможность использовать эту лазейку на пути первого примера (но не вторая, предполагая, что p не указывает на фактическое char [4]), является непреднамеренным последствием, которое стандартное только не может запретить из-за неточной формулировки.

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

Ответ 2

Мое чтение правил псевдонимов (C99, 6.5p7) с присутствием этого предложения:

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

приводит меня к мысли, что это не нарушает правила сглаживания С.

Но тот факт, что он не нарушает правила псевдонимов, недостаточно для того, чтобы этот фрагмент кода был действительным. Он может вызывать поведение undefined по другим причинам.

(struct s *) &x

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

EDIT: ответ был полностью переработан из его начальной версии

Ответ 3

Не уверен, что это правильный ответ, но что может произойти (во втором примере):

  • Компилятор определяет struct a как 8-байтовый объект с заполнением после 4 байтов в массиве (почему? потому что он может).
  • Затем вы используете tmp.r = *(struct a *)p;, который рассматривает p как адрес struct a (а именно, 8-байтовый объект). Он пытается скопировать содержимое этого объекта в tmp.r, то есть 8 байтов с адреса, который удерживается p. Но вам разрешено читать только 4 байта.

Реализации не должны копировать байты заполнения, но им разрешено делать это.

Ответ 4

В вашем втором примере

struct a { char r[4]; };

этот тип структуры может иметь некоторые ограничения выравнивания. Компилятор может решить, что struct a всегда выравнивается по 4 байт, например, так, что он всегда может использовать выровненную по 4 байта инструкцию чтения, не глядя на фактический адрес. Указатель p, который вы получаете как аргумент read32, не имеет такого ограничения, поэтому

*(struct a*)p;

может вызвать ошибку шины.

Я замечаю, что этот тип аргумента является "практическим".

С точки зрения стандарта это UB, как только (struct a*)p является преобразованием в тип с более строгими требованиями к выравниванию.

Ответ 5

Из стандарта C:

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

Результирующий указатель в этом случае гарантированно будет правильно выровнен (поскольку первый член структуры должен совпадать со структурой), поэтому это ограничение здесь не применяется. Применяются дополнительные ограничения на использование указателя, требующие, чтобы доступ к объекту осуществлялся только с помощью указателей, совместимых с "эффективным типом" объекта... в этом случае эффективный тип x равен int, и поэтому он не может быть доступен с помощью указателя struct.

Обратите внимание, что, вопреки некоторым утверждениям, преобразование между типами указателей не ограничивается использованием в обе стороны. В стандарте говорится, что указатель может быть преобразован с условием, когда такие преобразования приводят к поведению undefined. В другом месте он дает семантику использования указателей результирующего типа. Гарантией в оба конца в стандарте являются дополнительные спецификации... вещи, на которые вы можете рассчитывать, что вы не можете, если не указано явно:

В противном случае при преобразовании обратно результат сравняется с исходным указателем.

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

Однако, как отмечено, язык "эффективного типа" является ограничением использования указателя, полученного в результате преобразования.