Различия и масштаб декларации?
В вопросе Разница в заявлении? было задано вопрос, какая разница между
int i;
for (i=0; i<100; i++) {
//some loop....
}
и
for (int i=0; i<100; i++) {
//some loop....
}
Ответы ясны; второй - C99, а область i
ограничена циклом. У меня нет C99, поэтому я не могу проверить и, следовательно, задаю его как вопрос: какая будет резолюция в следующем случае:
int i = 32;
for (int i=i; i<100; i++) {
// some loop
}
Будет ли инициализирован "новый" i
с помощью "старого" i
? Или старый i
уже был бы недоступен, потому что новый i
уже был объявлен?
Ответы
Ответ 1
В этом для оператора цикла
int i = 32;
for (int i = i; i < 100; i++) {
// some loop
}
variable i
, объявленная в операторе for, имеет неопределенное значение. Проблема заключается в том, что как только определитель определен (в этом случае он состоит из идентификатора i
), он скрывает объект с тем же именем в данной области. Итак, в этой декларации
int i = i;
переменная я относится к себе в правой части =
.
Другой подобный пример. Предположим, что у вас есть typedef.
typedef int Int;
Вы можете написать после определения
Int Int;
В этом случае имя Int
объекта типа Int
скрывает определение typedef, и вы уже не можете писать
Int Another_Int;
потому что компилятор выдаст ошибку.
В соответствии со стандартом C (6.2.1 Области идентификаторов)
4... Если декларатор или спецификатор типа, который объявляет идентификатор появляется внутри блока или в списке параметров объявления в определении функции, идентификатор имеет область блока, который заканчивается в конце связанного блока.
Более ясно написано в стандарте С++ (пункт 3.3.2. декларации)
1 Точка объявления для имени сразу после его полный декларатор (раздел 8) и перед его инициализатором (если есть), за исключением случаев, указанных ниже. [Пример:
int x = 12;
{ int x = x; }
Здесь второй x инициализируется с его собственным (неопределенным) значением. -end пример]
Учтите, что в этом фрагменте кода
int i = 10;
{
int i[i];
}
внутри составного оператора объявлен массив int i[10];
, который является внешней переменной i
, используется как размер массива, потому что внутренняя переменная i
будет объявлена только после завершения ее декларатора.
Ответ 2
См. C11 6.8.5.3: "Если предложение-1 является объявлением, область любых идентификаторов, которые он объявляет, является остатком декларации и весь цикл, включая два других выражения".
Второй i
относится к i
, а не к старому.
Все это UB, потому что вы используете значение i
(i
определяется внутри цикла) без предыдущего назначения (или инициализации).
Изменить с помощью рабочего (но другого) примера
Вы все равно можете использовать старое значение с помощью указателя
int i = 42;
int *old_i = &i;
for (int i = *old_i; i < 50; i++) printf("%d ", i);
Ответ 3
Нет, в любом контексте что-то вроде
int i=i;
- плохая идея, поскольку второй i
является тем же самым объектом, что и первый, и все еще не инициализирован.
Если вы настаиваете, что можете сделать что-то вроде
int tmp=i, i=tmp;
чтобы иметь тот эффект, который вы хотите.
Ответ 4
Благодаря Йенсу Густедту я сделал еще несколько исследований и вот что нашел: Следующий код:
#include<stdio.h>
int main()
{
int i = 32;
for (int i=i; i<50; i++) {
printf("Hello World %d", i); // <-- this is the new i, the other one is not accessible here
}
return 0;
}
создать эту сборку с помощью gcc 5.2:
.LC0:
.string "Hello World %d"
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-8], 32
.L3:
cmp DWORD PTR [rbp-4], 49
jg .L2
mov eax, DWORD PTR [rbp-4]
mov esi, eax
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
add DWORD PTR [rbp-4], 1
jmp .L3
.L2:
mov eax, 0
leave
ret
Переменная, используемая для цикла, это [rbp-4]
, которая не инициализирована и может быть любой. Так что это действительно UB.