Объединение с константой и неконстантным членом?

Это похоже на неопределенное поведение

union A {
  int const x;
  float y;
};

A a = { 0 };
a.y = 1;

В спецификации говорится

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

Но ни один компилятор меня не предупредит, а ошибку легко диагностировать. Я неправильно истолковываю формулировку?


person Johannes Schaub - litb    schedule 13.04.2011    source источник
comment
Вы не всегда или даже обычно не получаете предупреждения о неопределенном поведении. Голосование за закрытие после того, как вы ответили на свой вопрос ...   -  person BlueRaja - Danny Pflughoeft    schedule 13.04.2011
comment
@BlueRaja litb не спрашивает, почему компилятор не предупреждает меня, он спрашивает, что компилятор не предупреждал меня, потому что я неверно истолковал спецификацию?   -  person JaredPar    schedule 13.04.2011
comment
@Blue нет причин, по которым компилятор не предупреждал бы или не выдавал ошибку, чтобы легко диагностировать ошибку, просто ища члены константного союза в объединении с неконстантными членами. Каждый компилятор, к которому я имею доступ, предупреждает о void f() { int a; ++a = ++a; }. Также применимо то, что говорит @JaredPar :)   -  person Johannes Schaub - litb    schedule 13.04.2011
comment
Где в спецификации это от. Иногда помогает чтение окружающего контекста.   -  person Martin York    schedule 13.04.2011
comment
Это значение составляет 3,8 [basic.life] / 9 как в C ++ 03, так и в C ++ 0x FDIS (N3290).   -  person James McNellis    schedule 13.04.2011


Ответы (3)


Последний проект стандарта C ++ 0x четко об этом говорит:

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

Итак, ваше заявление

a.y = 1;

это нормально, потому что он изменяет активный член с x на y. Если вы впоследствии укажете a.x как rvalue, поведение будет неопределенным:

cout << a.x << endl ; // Undefined!

Ваша цитата из спецификации здесь не актуальна, потому что вы не создаете никаких новых объектов.

person TonyK    schedule 13.04.2011
comment
Но он уничтожает a.x и создает a.y для создания нового объекта в месте хранения, которое ранее занимал объект const с автоматической продолжительностью хранения (см. 3.8p1, мы повторно используем хранилище a.x для a.y). Не могли бы вы более подробно объяснить, почему это не применимо? - person Johannes Schaub - litb; 13.04.2011
comment
@Johannes: a.y = 1 заканчивается ли срок службы a.x повторным использованием памяти? Если это так, то часть спецификации, которую вы цитируете, не нарушается. Но я так или иначе не знаю, так ли это, это довольно тонкое различие в том, что означает повторное использование памяти. Как говорит TonyK, текст о союзах изобретает свою собственную терминологию, а не говорит о времени существования объекта. - person Steve Jessop; 13.04.2011
comment
@Steve, если он не завершит время жизни a.y, тогда мы нарушим правила псевдонима, потому что мы изменим сохраненное значение объекта int const на lvalue типа float в этом примере: union A { int x; float y; }; A a = { 0 }; a.y = 1;. - person Johannes Schaub - litb; 13.04.2011
comment
@Johannes: присвоение a.y не уничтожает a.x (у которого в любом случае нет деструктора) - оно просто делает a.x недействительным. Он тоже ничего не создает, он просто делает a.y действительным. Никакой записи о том, какой член объединения действителен, не ведется в любое время, поэтому программист должен следовать правилам. - person TonyK; 13.04.2011
comment
+1 за правильный ответ - фрагмент OP действителен, член const просто не может быть повторно активирован. - person Erik; 13.04.2011
comment
@Johannes: извините, я не понимаю, что вы имеете в виду. Закончить срок службы y? Я сказал, что конец жизни x. Если он действительно завершает время жизни x, тогда мы не создаем новый объект в хранилище, занимаемом const до окончания его времени жизни, и, следовательно, ваш код не является UB. - person Steve Jessop; 13.04.2011
comment
@Johannes: а может, я неправильно прочитал. Означает ли это, что создание объекта в памяти, который когда-либо был занят константным объектом, - это UB? Даже если, например, объект const был создан с новым размещением и был разрушен? Ой. - person Steve Jessop; 13.04.2011
comment
@ Стив: да, извини. Я имею в виду конец жизни a.x. В тексте также говорится или в месте хранения, которое такой константный объект занимал до истечения его срока службы, что затем будет применяться здесь. - person Johannes Schaub - litb; 13.04.2011
comment
@ Йоханнес: это жестоко. Итак, struct Foo { const int a; }; char x[sizeof(Foo)]; Foo *p = new (x) Foo(); p->~Foo(); x[0] = 0; - это неопределенное поведение? x[0] находится в местоположении константного объекта до окончания его жизненного цикла. Или даже если x[0] = 0; не создает объект, повторение new (x) Foo();, безусловно, создает. - person Steve Jessop; 13.04.2011
comment
О нет, подожди. a там имеет динамическую продолжительность хранения, поэтому не подлежит оплате. Не так уж и жестоко :-) Но x в вашем примере имеет автоматическую продолжительность, поэтому нет. - person Steve Jessop; 13.04.2011
comment
Разработчики @TonyK GCC (некоторые из них являются членами комитета, например Майк Стамп) интерпретируют правило как я: gcc.gnu.org/bugzilla/show_bug.cgi?id=29286 - person Johannes Schaub - litb; 13.04.2011
comment
@litb: Другой член комитета (Джеймс Деннет), похоже, согласен с интерпретацией TonyK - см. mail-archive.com/[email protected]/msg39958.html - person Erik; 13.04.2011
comment
@Johannes: Помогите мне здесь. Вы предоставляете ссылку на 3000-строчный отчет об ошибке и ожидаете, что я найду соответствующее предложение? Даже поиск Mike Stump оставил мне сотни строк. - person TonyK; 13.04.2011
comment
@Erik Я не вижу, чтобы он противоречил тому, что я сказал. Да, он изменяет активного члена с a.x на a.y. Но это не означает, что он не создает новый объект в том месте хранения, которое константный объект ... раньше занимал. Насколько я понимаю, Джеймс не противоречит этой точке зрения. - person Johannes Schaub - litb; 13.04.2011
comment
@TonyK извини. Вот прямая ссылка на соответствующий комментарий: gcc.gnu.org/ bugzilla / show_bug.cgi? id = 29286 # c16. - person Johannes Schaub - litb; 13.04.2011
comment
@litb: я прочитал, что один константный член в порядке, два или более - нет. Я его спрошу: P - person Erik; 13.04.2011
comment
@Erik Я согласен с этим. Один константный член в порядке. То есть следующее прекрасно: union A { int const a; int b; } a = { 0 };. - person Johannes Schaub - litb; 13.04.2011
comment
@litb: Значит, вы говорите, что стандарт противоречит сам себе в этом? Я не понимаю, как активация члена союза может рассматриваться как создание нового объекта, в то время как в то же время константный член союза действителен (с учетом вашей исходной цитаты)? - person Erik; 13.04.2011
comment
@Johannes: Я не удивлен, что не нашел ваш «соответствующий комментарий». При чем тут профсоюзы? - person TonyK; 14.04.2011
comment
@Erik Я не хочу сказать, что спецификация противоречит сама себе. Я думаю, что a.y = 0 создает объект float, точно так же, как void *p = malloc(max(sizeof(int), sizeof(float))); *(int*)p = 0; *(float*)p = 0.f; сначала создает объект int, а затем создает объект float. Я не понимаю, почему вы думаете, что наличие константного члена объединения противоречит представлению о том, что запись в член объединения создаст новый объект. - person Johannes Schaub - litb; 14.04.2011
comment
@Johannes Schaub - litb: если a.y = 0 создает float, это означает, что существующая const int также уничтожается, что нарушает вашу исходную стандартную цитату, поэтому, если она применяется, у вас не может быть константного члена союз. Но если вы можете на законных основаниях иметь константный член союза (как, кажется, подразумевают косвенные доказательства), то активация / деактивация не может считаться созданием / уничтожением или стандартом является противоречит самому себе. Или? - person Erik; 14.04.2011
comment
@Erik Новый объект с плавающей запятой будет занимать область памяти, которую раньше занимал объект const int. Следовательно, это будет неопределенное поведение. таким образом, если это применимо, у вас не может быть константного члена союза - ›Почему это так? Бывают ситуации, когда это не применяется и у нас все еще есть константный член. Я привел пример выше: union A { const int x; float y; } a = { 0 };. Это не неопределенное поведение. Конечно, если вы сейчас напишете a.y, у вас будет неопределенное поведение. Но нет никакой спецификации, противоречащей этому. - person Johannes Schaub - litb; 14.04.2011
comment
Я думаю, что активный член - это просто хорошо звучащий термин, который спецификация использует, чтобы сказать создание объекта типа этого члена. В этом нет более глубокого смысла. Вы можете присвоить старому объекту псевдоним, который определен в 3.10 / 15 для определенных случаев. Как struct A { int x; unsigned char y; }; A a = { 10 }; unsigned char y = a.y;, что совершенно верно, хотя и похоже на чтение неактивного члена. Это то, что, по-видимому, имеет в виду Джеймс Деррет, когда говорит, что в некоторых случаях можно набирать текст через объединение. - person Johannes Schaub - litb; 14.04.2011
comment
@Johannes Schaub - litb: Используя ваш пример union A { const int x; float y; } a = { 0 };, я думаю, что UB вызывается не тогда, когда вы пишете в a.y, а если после этого вы читаете из a.x - Если активация - это создание, то объединение с константным членом бесполезно - вы не можете когда-либо делать что-либо, кроме чтения члена const, не вызывая UB. Если активация не является созданием, вы можете по крайней мере использовать объединение как обычно, вы просто не можете повторно активировать член const после активации чего-то еще. Но это определенно не ясно из стандарта 03. - person Erik; 14.04.2011
comment
@Erik Я согласен с тем, что UB вызывается в любом случае, если вы впоследствии читаете из a.x (после того, как написали a.y). Наши разногласия касаются того, происходит ли UB раньше. Это крайний случай, поэтому я думаю, что в большинстве случаев это нормально, если поведение undefined. Спецификация не специально разрешает постоянным членам профсоюзов в случае, если они очень полезны или что-то в этом роде. Это просто вытекает из существующих правил, и бывает, что большинство применений не определены. - person Johannes Schaub - litb; 14.04.2011
comment
@Johannes Schaub - litb: Итак, чего не хватает стандарту, так это четкого указания на активацию - создание это или нет. И да, это во многом крайний случай - хотя попытаться разобраться в нем все еще забавно. - person Erik; 14.04.2011

На самом деле нет смысла иметь const член union, и я удивлен, что стандарт позволяет это. Целью всех многочисленных ограничений на то, что может входить в union, является достижение точки, в которой побитовое присваивание будет действительным оператором присваивания для всех членов, и вы не сможете использовать побитовое присваивание для присваивания const int. Я предполагаю, что это просто случай, о котором никто раньше не думал (хотя он влияет на C так же, как и на C ++, поэтому он существует уже некоторое время).

person James Kanze    schedule 13.04.2011
comment
Разве это неразумно - хотеть, чтобы член союза был доступен только для чтения? Например, если я использую объединение для проверки байтов с плавающей запятой по одному, мне может понадобиться union { float f; const unsigned char b[sizeof(float)]; }, если бы это имело желаемый / ожидаемый эффект принудительного выполнения того, что я не пишу побайтно, а только читаю. Конечно, в этом примере я мог бы просто привести к unsigned char*, поэтому, возможно, мне понадобится еще один, но выбор типов через объединения не всегда гарантируется, поэтому это зависит от реализации, что это на самом деле вас выигрывает. - person Steve Jessop; 13.04.2011
comment
Я имею в виду, бросить на const unsigned char*! - person Steve Jessop; 13.04.2011
comment
Имеет смысл иметь const члена union. Его можно инициализировать при создании union, но не позже. Если впоследствии будет назначен какой-либо другой член union, то член const навсегда станет неопределенным. - person TonyK; 13.04.2011
comment
Мне жаль. Я изменил тип второго члена на float, чтобы не было путаницы, что он как-то связан с int vs int const или чем-то еще. - person Johannes Schaub - litb; 14.04.2011
comment
В чем смысл постоянного члена союза? Нет смысла использовать союз, если вы не собираетесь его менять; в противном случае сработает переменная исходного типа. - person James Kanze; 14.04.2011
comment
@JamesKanze: возможно, дело в том, чтобы реализовать геттер только для чтения. - person sam hocevar; 06.03.2012
comment
@JamesKanze: union можно комбинировать с шаблоном прокси. См., Например, gist.github.com/1987477 не очень элегантный, но, надеюсь, иллюстративный пример. - person sam hocevar; 06.03.2012
comment
@SamHocevar Я не уверен, что код там вообще законный; его точно нет в C ++ 03. И даже если бы это было так, я не понимаю, что union вам покупает. - person James Kanze; 06.03.2012
comment
@JamesKanze: union допускает синтаксис получения и установки, подобный C # и GLSL (без скобок), при этом обеспечивая компактность (поскольку нет необходимости хранить this в прокси). Из любопытства, ломает ли он что-нибудь в C ++ 03, кроме анонимного struct? - person sam hocevar; 06.03.2012
comment
@SamHocevar Объединение не может содержать типы с нетривиальными конструкторами, деструкторами или операторами присваивания. И даже в C ++ 11 я не думаю, что существуют анонимные структуры, и я почти уверен, что если вы получите доступ к элементу объединения, отличному от последнего, хранящегося в нем, это будет неопределенное поведение. (Таким образом, доступ к norm, если последний сохраненный элемент был x или y в вашем коде, является неопределенным поведением.) - person James Kanze; 06.03.2012
comment
@JamesKanze: в приведенном выше коде нет нетривиальных конструкторов, деструкторов или операторов присваивания. Кроме того, часть стандарта C ++, на которую вы ссылаетесь, является одной из наиболее неверно истолкованных, и я считаю, что правильно обращаться к членам unions, если они имеют тот же тип и адрес. Или вы намекаете, что union { int i; int j } u; u.i = 1; cout << u.j; - это UB? (кроме того, каждый известный мне компилятор поддерживает набор символов через unions, но это уже другая история) - person sam hocevar; 06.03.2012
comment
@SamHocevar Я упустил тот факт, что у Norm нет конструкторов. Но у него есть частные данные-члены, и без конструкторов их невозможно инициализировать. Официально ваш короткий пример - это неопределенное поведение (хотя я тоже был бы очень удивлен, если бы он где-нибудь потерпел неудачу). Однако ваш случай более сложный - структура с int и Norm даже несовместима с макетом. - person James Kanze; 06.03.2012

Если это утешает - компилятор Microsoft Xbox 360 (который основан на компиляторе Visual Studio) выдает ошибку. Что забавно, потому что это обычно самый снисходительный из всех.

error C2220: warning treated as error - no 'object' file generated
warning C4510: 'A' : default constructor could not be generated
    : see declaration of 'A'
warning C4610: union 'A' can never be instantiated - user defined constructor required

Эта ошибка исчезнет, ​​если я уберу const. Компиляторы на основе gcc не жалуются.

РЕДАКТИРОВАТЬ: компилятор Microsoft Visual C ++ имеет такое же предупреждение.

person EboMike    schedule 13.04.2011
comment
Visual C ++ 2010 дает такое же предупреждение (с /W4). Это предупреждение (C4610) интересно тем, что оно неверно: A действительно может быть создан, как продемонстрировано A x = { 0 }; (что Visual C ++ 2010 принимает). - person James McNellis; 13.04.2011
comment
На самом деле, похоже, Visual Studio C ++ не любит неинициализированные const int, точка. Я попытался добавить const int x в глобальную область видимости, и это тоже создало предупреждение. Имеет смысл, поскольку компилятор обрабатывает const int как константы времени компиляции. - person EboMike; 13.04.2011
comment
@ Йоханнес: Верно. Я просто предполагаю, что компилятор отключает обработку const int и настаивает на том, что const int необходимо инициализировать с помощью конструктора или начального присваивания, и не учитывает ={0} в объединениях. - person EboMike; 13.04.2011
comment
Подводя итог - я считаю, что вы правы, это неопределенное поведение, и Visual C ++ случайно предупреждает вас об этом, хотя и не специально, а из-за неумелости. - person EboMike; 13.04.2011