Union 'punning' structs w/ "common initial sequence": Почему C (99+), но не С++, предусматривает "видимое объявление типа объединения"?

Фон

Дискуссии о типичном характере типа-punning через union, описывающем не типичную реализацию, типично цитируют следующие биты, здесь через @ecatmur (qaru.site/info/94985/...), на исключение для стандартного макета struct, имеющего "общую начальную последовательность" типов членов:

C11 (6.5.2.3 Элементы структуры и объединения; Семантика):

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

С++ 03 ( [класс .mem]/16):

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

Другие версии двух стандартов имеют схожий язык; поскольку С++ 11 используемая терминология - это стандартная макета, а не POD.

Поскольку переинтерпретация не требуется, на самом деле это не типизация, а просто замена имени, примененная к union доступ к элементам. Предложение для С++ 17 (печально известный P0137R1) делает это явным использованием языка как "доступ как будто был назначен другой член структуры".

Но обратите внимание на жирный шрифт ", где вид объявления завершенного типа объединения виден" - предложение, которое существует в C11, но нигде в проектах С++ для 2003, 2011 или 2014 (все почти идентичные, но более поздние версии заменяют "POD" новым стандартным макетом термина). В любом случае "видимое объявление бит типа union полностью отсутствует в соответствующем разделе любого стандарта С++.

@loop и @Mints97, здесь qaru.site/info/94992/... - показывают, что эта строка также была отсутствующей на C89, впервые появившейся в C99 и оставаясь в C с тех пор (хотя, опять же, никогда не отфильтровывая до С++).

Обсуждения стандартов вокруг этого

[snipped - см. мой ответ]

Вопросы

Отсюда и мои вопросы:

  • Что это значит? Что классифицируется как "видимое объявление"? Был ли этот пункт предназначен для сужения или расширения - диапазона контекстов, в которых такое "наказание" определяло поведение?

  • Предполагаем ли мы, что это упущение в С++ очень преднамеренное?

  • В чем причина того, что С++ отличается от C?. С++ просто "наследовал" это от C89, а потом либо решил - или, что хуже, забыл - обновить рядом с C99?

  • Если разница является преднамеренной, то какие преимущества или недостатки существуют для двух разных обработок в C vs С++?

  • Что, если таковые имеются, интересные разветвления у него есть при компиляции или времени выполнения? Например, @ecatmur, в комментарии, отвечая на мое указание на его оригинальный ответ (ссылка как указано выше), предположил следующее.

Я бы предположил, что это позволяет более агрессивно оптимизировать; C может предположить, что аргументы функции S* s и T* t не являются псевдонимами, даже если они совместно используют общая начальная последовательность, если нет union { S; T; }, в то время как С++ может сделать это предположение только во время связи. Может стоить задавая отдельный вопрос об этой разнице.

Ну, вот я, спрашиваю! Меня очень интересуют любые мысли об этом, особенно: другие соответствующие части (или) Стандарта, цитаты из членов комитета или других уважаемых комментаторов, идеи от разработчиков, которые, возможно, заметили практическую разницу из-за этого - при условии, что любой компилятор даже препятствует принудительному добавлению предложения C и т.д. Цель состоит в том, чтобы создать полезный каталог соответствующих фактов об этом предложении C и его (намеренное или нет) упущение с С++. Итак, отпусти!

Ответ 1

Я нашел свой путь через лабиринт к некоторым великим источникам на этом, и я думаю, что у меня есть довольно полное резюме этого. Я отправляю это как ответ, потому что он, кажется, объясняет как (ИМО очень ошибочное) намерение предложения C, так и тот факт, что С++ не наследует его. Это со временем изменится, если я обнаружу дальнейший вспомогательный материал или изменения ситуации.

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

Наконец, некоторые конкретные комментарии

Смутно связанные потоки, я нашел следующий ответ от @tab - и высоко оценил содержащиеся ссылки на (освещающие, если не убедительные) отчеты GCC и Рабочей группы о дефектах: fooobar.com/questions/94984/...

Ссылка GCC содержит некоторое интересное обсуждение и показывает значительную путаницу и противоречивые интерпретации со стороны Комитета и поставщиков компиляторов - вокруг темы union member struct s, punning и aliasing как в C, так и в С++.

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

Происхождение добавленной строки в C99

Предложение N685 является источником добавленного предложения относительно видимости объявления типа union. Посредством того, что некоторые претензии (см. Поток GCС# 2) является полной неверной интерпретацией "общей исходной последовательности", N685 действительно был , чтобы разрешить релаксацию правил сглаживания для "общей исходной последовательности" struct в рамках TU некоторых union, содержащих экземпляры указанных struct типов, как видно из этой цитаты:

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

union utag {
    struct tag1 { int m1; double d2; } st1;
    struct tag2 { int m1; char c2; } st2;
};

int similar_func(struct tag1 *pst2, struct tag2 *pst3) {
     pst2->m1 = 2;
     pst3->m1 = 0;   /* might be an alias for pst2->m1 */
     return pst2->m1;
}

Судя по обсуждению GCC и комментариям ниже, таким как @ecatmur, это предложение, которое, по-видимому, требует спекулятивного разрешения псевдонимов для любого типа struct, имеющего некоторый экземпляр внутри некоторого union, видимого для этого TU - чтобы получить отличную насмешку и редко выполнялись.

Очевидно, насколько сложно было бы удовлетворить эту интерпретацию добавленного предложения без полного искажения многих оптимизаций - для небольшой выгоды, поскольку малое количество кодеров захотело бы этой гарантии, а те, кто это делает, могут просто включить fno-strict-aliasing (что ИМО указывает на большие проблемы). Если это реализовано, это пособие с большей вероятностью поймает людей и ложно взаимодействует с другими объявлениями union s, чем полезно.

Отсутствие строки из С++

Следуя этому и комментарию, который я сделал в другом месте, @Potatoswatter в этом ответе здесь, на SO, говорится, что:

Часть видимости была намеренно исключена из С++, потому что она широко считается смехотворной и нереализуемой.

Другими словами, похоже, что С++ сознательно избегал принятия этого добавленного предложения, вероятно, из-за его широко воспринимаемой абсурдности.. По просьбе "на запись", цитируя это, Potatoswatter предоставил следующее ключевая информация о участниках темы:

Люди в этом обсуждении, по сути, "находятся на записи". Эндрю Пински - хардкорный сторонник GCC. Мартин Себор является активным членом комитета C. Джонатан Вакели является активным членом комитета С++ и разработчиком языка/библиотеки. Эта страница является более авторитетной, ясной и полной, чем все, что я мог бы написать.

Potatoswatter, в том же потоке SO, который был связан выше, делает вывод о том, что С++ сознательно исключил эту строку, не оставляя специальной обработки (или, в лучшем случае, обработки, определенной реализацией) для указателей в общую начальную последовательность. Будет ли их лечение в будущем конкретно определено, по сравнению с любыми другими указателями, еще предстоит выяснить; сравните с моим заключительным разделом ниже о C. В настоящее время, однако, это не так (и снова IMO, это хорошо).

Что это значит для С++ и практических реализаций C?

Итак, с гнусной линией от N685... "отбросьте"... мы вернулись к предположению, что указатели на общую начальную последовательность не являются особыми с точки зрения сглаживания. Все еще. стоит подтвердить, что этот абзац на С++ означает без него. Ну, второй поток GCC над ссылками на другой камень:

С++ дефект 1719. Это предложение достигло статуса DRWP: "Проблема DR, разрешение которой отражено в текущем рабочем документе. Рабочий документ представляет собой проект для будущей версии стандарта" - cite. Это либо пост С++ 14, либо, по крайней мере, после окончательного проекта, который я здесь привел (N3797), и намеревается сделать значительным и, по моему мнению, осветить, переписать текст этого параграфа следующим образом. Я смелый, что я считаю важными изменениями, и {эти комментарии} являются моими:

В стандартном макете с активным членом { "active" указывает экземпляр union, а не только тип} (9.5 [class.union]) типа struct T1, разрешено читать {ранее "проверять" } нестатический элемент данных m другого члена объединения типа struct T2 при условии, что m является частью общая начальная последовательность T1 и T2. [Примечание: чтение изменчивого объекта через нелетучее значение glvalue имеет поведение undefined (7.1.6.1 [Dcl.type.cv]). -end note]

Это, по-видимому, разъясняет смысл старой формулировки: для меня это говорит о том, что любое специально разрешенное 'punning' среди union member struct с общими начальными последовательностями должно выполняться через экземпляр родительского union - вместо того, чтобы основываться на типе structs (например, указатели на них переданы некоторой функции). Эта формулировка, по-видимому, исключает любую другую интерпретацию, a la N685. Я бы сказал, что C будет хорошо принять это. Эй, говоря об этом, см. Ниже!

В результате получается, что - как хорошо продемонстрировано @ecatmur и в GCC-билетах - это оставляет такой union member struct по определению в С++ и практически на C, подчиняясь тем же строгим правилам псевдонимов как и любые другие 2 официально несвязанных указателя. Явная гарантия того, что вы можете прочитать общую начальную последовательность неактивного union члена struct, теперь более четко определена, не считая неопределенной и невообразимо утомительной для обеспечения соблюдения "видимость", как было предпринято N685 для C. В соответствии с этим определением основные компиляторы ведут себя как предназначенные для С++. Что касается C?

Возможное изменение этой строки в C/clarification в С++

Также очень важно отметить, что член комитета C Мартин Себор надеется, что это исправлено и на этом прекрасном языке:

Мартин Себор 2015-04-27 14:57:16 UTC Если кто-то из вас сможет объяснить проблему, я готов написать документ и представить его в РГ14 и запросить изменить стандарт.

Мартин Себор 2015-05-13 16:02:41 UTC У меня была возможность обсудить эту проблему с Кларком Нельсоном на прошлой неделе. Кларк работал над улучшением части псевдонимов спецификации C в прошлом, например, в N1520 (http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1520.htm). Он согласился с тем, что, как и проблемы, упомянутые в N1520, это также является выдающейся проблемой, которая стоит для пересмотра и исправления РГ14 ".

Potatoswatter вдохновляет:

Комитеты C и С++ (через Мартина и Кларка) попытаются найти консенсус и выработать формулировку, чтобы стандарт мог наконец сказать, что это значит.

Мы можем только надеяться!

Снова, все дальнейшие мысли приветствуются.

Ответ 2

Я подозреваю, что это означает, что доступ к этим общим частям разрешен не только через тип объединения, но и за пределами союза. То есть предположим, что мы имеем это:

union u {
  struct s1 m1;
  struct s2 m2;
};

Теперь предположим, что в некоторой функции мы имеем указатель struct s1 *p1, который, как мы знаем, был взят из члена m1 такого объединения. Мы можем применить это к указателю struct s2 * и по-прежнему обращаться к членам, которые являются общими для struct s1. Но где-то в области видимости должно быть видно объявление union u. И это должно быть полное объявление, которое сообщает компилятору, что члены struct s1 и struct s2.

Вероятное намерение состоит в том, что если в области есть такой тип, то у компилятора есть знания, что struct s1 и struct s2 являются псевдонимами, и поэтому доступ через указатель struct s1 * подозревается в действительно доступе к struct s2 или наоборот.

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

Так как формулировка отсутствует на С++, то, чтобы воспользоваться преимуществом правила "обычного начального члена релаксации" на этом языке, вы должны маршрутизировать обращения через тип объединения, как это обычно делается:

union u *ptr_any;
// ...
ptr_any->m1.common_initial_member = 42;
fun(ptr_any->m2.common_initial_member);  // pass 42 to fun