Можно ли ссылаться на переменную в ее определении?

int test[2] = { 45, test[0] };
int x = (x = 111);
cout << test[0] << " " << test[1] << " " << x << "\n"; // 45 45 111

Допустимы ли присваивания в первых двух строках? Visual Studio 2010 компилирует и запускает его без каких-либо ошибок или предупреждений, но это кажется странным случаем, который может быть неопределенным, поэтому я хотел подтвердить, что это приемлемо. Visual Studio предупреждает меня, если я делаю что-то явно рефлексивное (и предположительно неопределенное), например int x = x;, поэтому мне интересно, как обрабатываются эти ситуации, которые он, кажется, разрешает.


person 0x5f3759df    schedule 09.11.2012    source источник
comment
Ничего не стоит то, что int x = (x + 1) также компилирует и устанавливает x в 1 (во всяком случае, с GCC в Linux).   -  person apsillers    schedule 09.11.2012
comment
@apsillers: вы полагаетесь на неопределенное поведение. x не инициализируется при оценке (x + 1).   -  person Jonathan Grynspan    schedule 09.11.2012
comment
Это компилируется и в linux. Результат: 45 45 111 Действительно странный случай, но кажется, что это правильно! Я думаю, компилятор делает свое дело. И когда эти строки преобразуются в сборку, они находятся в таком порядке, что используемые значения уже существуют.   -  person Paschalis    schedule 09.11.2012
comment
Это (int x = (x + 1);) явно неопределенное поведение; вы увеличиваете неопределенное значение, которое не обязательно равно нулю.   -  person Jonathan Leffler    schedule 09.11.2012
comment
@apsillers Visual Studio выдает мне предупреждение о x = (x + 1) и случайном выводе мусора (как я и ожидал).   -  person 0x5f3759df    schedule 09.11.2012
comment
@JonathanGrynspan Я думал, что могу, поэтому я указал свою платформу для справки. Инициализирует ли GCC целые числа 0, или мне просто повезло? Можешь не отвечать здесь - я отклоняюсь от темы.   -  person apsillers    schedule 09.11.2012
comment
@apsillers Дайте угадаю ... вы поместили int x = (x + 1); в область файла (или пространства имен). Попробуйте поместить его в функцию.   -  person Praetorian    schedule 09.11.2012
comment
@apsillers: Вам повезло. В моем GCC значение по умолчанию — potato.   -  person Jonathan Grynspan    schedule 09.11.2012
comment
Связано: stackoverflow.com/questions/8595061/   -  person Lightness Races in Orbit    schedule 09.11.2012


Ответы (4)


Из стандарта С++ (С++ 11, но в С++ 98/03 он не отличался):

(§ 3.3.2/1) точка объявления для имени находится сразу после его полного декларатора (пункт 8) и перед его инициализатором (если есть), [. ..] [ Пример:

int x = 12;
{ int x = x; }

Здесь второй x инициализируется собственным (неопределенным) значением. — конец примера]

Это относится как к пользовательским типам, так и к типам массивов. Обратите внимание, как стандарт подчеркивает, что x во втором примере инициализируется неопределенным значением. Таким образом, невозможно узнать, с каким значением инициализируется x.

person jogojapan    schedule 09.11.2012

Я предполагаю, что вы находитесь в какой-то функции, так как вы вызываете функции и тому подобное.

Место для test и x выделено в стеке. Теоретически место для этих парней должно существовать до того, как будут заполнены их значения. Если мы посмотрим на сгенерированную сборку (x86 gcc), то это правда.

subl    $40, %esp         # Add 40 bytes of memory to the current stack
movl    $0, -20(%ebp)     # Clear test[0] to 0
movl    $0, -16(%ebp)     # Clear test[1] to 0
movl    $45, -20(%ebp)    # Place the value of 45 into test[0]
movl    -20(%ebp), %eax  # Copy that 45 into a register
movl    %eax, -16(%ebp)  # Move that register's value (45) into test[1]
movl    $111, -12(%ebp)  # Assign x to be 111, optimize out the unnecessary duplicate assignment
    ... #continues on to set up and call printf

Мы видим, что в стек добавлено 40 байт. Обратите внимание, что адреса test[0], test[1] и x являются непрерывными адресами, отделенными от %ebp с интервалом в 4 байта (-20,-16,-12 соответственно). Их расположение в памяти существует, и к ним можно получить доступ без ошибок до того, как они будут определены. Здесь компилятор очищает их оба до 0, хотя мы видим, что это не нужно. Вы можете удалить эти две строки и все равно работать нормально.

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

person GraphicsMuncher    schedule 09.11.2012

Это законно, потому что объявление переменной завершено к моменту ее инициализации. Однако значение test[1] не определено.

person Jonathan Grynspan    schedule 09.11.2012
comment
Как? Это 45. Он берет значение в test[0] и записывает его в test[1]. Даже если бы это была циклическая ссылка (например, {test[1],test[0]}), они все равно были бы просто размещены в стеке с бессмысленными значениями, поэтому мусор из test[1] попал бы в test[ 0], а затем test[0] перейдет в test[1], сделав их обоих test[1]. - person GraphicsMuncher; 09.11.2012
comment
@GraphicsMuncher: Нет... Нет test[0] во время оценки будущего значения test[1]. И ваше обсуждение мест в стеке тоже не имеет для меня особого смысла. - person Lightness Races in Orbit; 09.11.2012
comment
@LightnessRacesinOrbit: инициализация test должна пройти нормально. Что значит нет test[0]? В С++ 03 есть точка последовательности после каждого инициализатора (я не смотрел, как это происходит с тех пор, как они были удалены), поэтому я почти уверен, что test[0] существует и инициализируется после достижения test[1]. - person GManNickG; 09.11.2012
comment
@GManNickG: я не думаю, что будет справедливо сказать, что test[0] уже инициализируется запятой в середине инициализатора. - person Lightness Races in Orbit; 09.11.2012
comment
@LightnessRacesinOrbit: test нет, а test[0] есть. - person GManNickG; 09.11.2012
comment
@GManNickG: до завершения инициализации test его значение неопределенно. Поэтому мы ничего не можем сказать о test[0]. - person Lightness Races in Orbit; 10.11.2012

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

person AJMansfield    schedule 09.11.2012
comment
Смотря что вы подразумеваете под юридическим. Синтаксически правильно? Конечно. Четко определен? Неа. Чтение неинициализированной переменной является поведением undefined. Это относится только к инициализации x. Инициализатор test в порядке. - person GManNickG; 09.11.2012
comment
-1: There are even circumstances in which you might actually even want to do it, too. Нет, абсолютно точно нет! - person Lightness Races in Orbit; 09.11.2012
comment
Я имею в виду, что если вы обобщите объявления для других типов, а не только для int, это действительно может привести к чему-то. Например, если тип массива содержит указатели на объекты, вы можете захотеть, чтобы второй элемент был копией другого, чтобы эти указатели ссылались на одни и те же объекты. Для второго может быть обстоятельство, когда operator = перегружен типом x. Хотя я согласен, что нет никаких причин, по которым любой здравомыслящий человек хотел бы делать это с ints. - person AJMansfield; 09.11.2012