Переменная, созданная внутри цикла, меняет значение во время итераций в C

У меня есть код, подобный следующему в нашем продукте. По моему мнению, результат равен "0 1 2 3". Но вывод подобного кода будет "1 1 1 1".

for(i = 0 ;i < 5;i++){
    int j;
    if(i)
        printf("%d ",j);
    j = i;
}

Насколько я понимаю, j выделяется в стеке только один раз за весь период цикла for, и одно и то же значение используется во время итераций. Кроме того, если я переместу объявление j за пределы цикла, я получу ожидаемый результат. Что мне здесь не хватает?

PS - Когда я запускаю тот же код на моем персональном компьютере, я получаю ожидаемый результат. Но на производстве все иначе.

2 ответа

Решение

Во-первых, чтобы прояснить вопрос о продолжительности хранения автоматической локальной переменной, позвольте мне процитировать C11 стандарт, глава §6.2.4, (выделение мое)

Объект, идентификатор которого объявлен без привязки и без спецификатора класса хранения static имеет автоматическую продолжительность хранения, [...]

а также,

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

Итак, в вашем коде каждая итерация получает новый экземпляр j, Ничего не сохраняется.

В вашем коде

    int j;   //not initialized
    if(i)
        printf("%d ",j);  //this one here

вы пытаетесь использовать унифицированную автоматическую локальную переменную j, который имеет неопределенное значение. Это вызывает неопределенное поведение.

Согласно C11 Глава §6.7.9

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

и связанные, для UB, приложение §J.2

Значение объекта с автоматическим сроком хранения используется, пока оно не определено.

Как только ваш код достигнет UB, вывод не может быть оправдан, в любом случае.

OTOH, когда вы объявляете j вне цикла он имеет область действия функции. Тогда, в отличие от приведенного выше случая, будет только один случай j для всех итераций цикла.

Согласно потоку выполнения, в первый раз, i быть 0, if будет оценивать как ложное, printf() будут пропущены и j будет инициализирован. Затем, в следующей итерации, когда вы нажмете printf(), j инициализируется и все хорошо после этого.

Для ясности, я думаю, что цикл for будет преобразован под капотом что-то вроде:

i = 0;
LoopStart:
if(!(i<5)){ goto LoopEnd;}

{
    int j;
    if(i)
        printf("%d ",j);
    j = i;
}

i++;
goto LoopStart;
LoopEnd:

Реальные реализации могут отличаться, но это служит для того, чтобы подчеркнуть этот момент: блок вводится и завершается для каждой итерации цикла, что означает, что каждое auto в блоке создается и уничтожается 5 раз в этом примере. как уже упоминалось, это означает, что вы используете неинициализированный j каждый раз в вашем printf.

Что касается того, почему код может работать на некоторых платформах / компиляторах. Вероятно, это потому, что j каждый раз выделяется один и тот же адрес стека, и компилятор не очищает стек при создании или уничтожении j, поэтому просто так происходит, что последнее значение, назначенное старому, dead j, доступно через новый, неинициализированный.

Другие вопросы по тегам