Подтвердить что ты не робот

Различия и масштаб декларации?

В вопросе Разница в заявлении? было задано вопрос, какая разница между

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 уже был объявлен?

4b9b3361

Ответ 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.