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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

 (struct s *) &x 

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

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

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

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

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

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

 struct a { char r[4]; }; 

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

 *(struct a*)p; 

может привести к ошибке шины.

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

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

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

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

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

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

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

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

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