union ‘punning’ structs w / “common initial sequence”: Почему C (99+), но не C ++, предусматривает «видимое объявление типа объединения»?

Задний план

В дискуссиях о типичном характере пуна в форме, не относящемся к реализации, через union обычно цитируются следующие биты, здесь, через @ecatmur ( https://stackoverflow.com/a/31557852/2757035 ), об освобождении от стандартного -layout struct s, имеющий «общую начальную последовательность» типов членов:

C11 ( 6.5.2.3 Состав и члены союза , Семантика ):

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

C ++ 03 ( [class.mem] / 16 ):

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

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

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

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

@loop и @ Mints97, здесь – https://stackoverflow.com/a/28528989/2757035 – показывают, что эта строка также отсутствовала на C89, сначала появляясь на C99 и оставаясь на C с тех пор (хотя, опять же, никогда не фильтруя на C ++).

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

[отрезанный – см. мой ответ]

Вопросов

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

  • Что это значит? Что classифицируется как «видимая декларация»? Был ли этот пункт предназначен для сужения или расширения – диапазона контекстов, в которых такое «наказание» определяло поведение?

  • Должны ли мы предположить, что это упущение на C ++ очень преднамеренное?

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

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

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

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

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

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

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

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

Через неопределенно связанные streamи я нашел следующий ответ от @tab – и высоко оценил содержащиеся ссылки на (освещающие, если не убедительные) отчеты GCC и Рабочей группы о дефектах: ответ на вкладку StackOverflow

Ссылка GCC содержит интересную дискуссию и показывает значительную часть путаницы и противоречивых интерпретаций со стороны Комитета и поставщиков компиляторов – вокруг темы struct члена union , казни и псевдонимов на C и C ++.

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

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

Предложение C N685 является источником добавленного предложения относительно видимости объявления типа union . Посредством того, что некоторые претензии (см. Поток GCC № 2) является полной неверной интерпретацией «общей исходной последовательности», N685 действительно предназначался для того, чтобы разрешить релаксацию правил псевдонимов для структуры «обычной исходной последовательности» в пределах 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 , чем полезно.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Martin Sebor 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 и C ++ (через Мартина и Кларка) попытаются найти консенсус и выработать формулировку, чтобы стандарт мог наконец сказать, что это значит.

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

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

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

 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 или наоборот.

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

Поскольку формулировка отсутствует на C ++, то, чтобы воспользоваться принципом релаксации «обычных начальных членов» на этом языке, вы должны маршрутизировать обращения через тип объединения, как это обычно делается:

 union u *ptr_any; // ... ptr_any->m1.common_initial_member = 42; fun(ptr_any->m2.common_initial_member); // pass 42 to fun 
Давайте будем гением компьютера.