Почему эта программа выдает странный результат, когда переменная не инициализирована?

int main() {
    int j = 0;
    int i = 0;
    for (j = 0; j < 5; j++) {
        printf("Iteration %d :  %d ", j + 1, i);
        int i;
        printf("%d", i);
        i = 5;
        printf("\n");
    }
}

Приведенный выше код генерирует следующий вывод:

Iteration 1 :  0 0
Iteration 2 :  0 5
Iteration 3 :  0 5
Iteration 4 :  0 5
Iteration 5 :  0 5

Я не могу понять, почему второе значение printf на итерациях 2,3,4,5 равно 5.

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

Но я не могу понять, почему это значение становится 5 на втором printf.


person Milan    schedule 23.02.2017    source источник
comment
Хороший вопрос, жаль, что ответ такой краткий.   -  person Bathsheba    schedule 23.02.2017
comment
@Bathsheba - Вы всегда можете сделать свой ответ намеренно подробным: P   -  person StoryTeller - Unslander Monica    schedule 23.02.2017
comment
Примечание: это называется затенением и настоятельно не рекомендуется, так как оно не приносит пользы, но очень подвержено ошибкам. Включите все рекомендуемые предупреждения компилятора, одно из них должно предупреждать о таких проблемных определениях.   -  person too honest for this site    schedule 23.02.2017
comment
Вы правы, что он выходит за рамки на каждой итерации, но, как правило, следующая итерация будет использовать ту же память, что и предыдущая, почему бы и нет, создавая видимость инициализации   -  person Mad Physicist    schedule 23.02.2017
comment
@MadPhysicist: Осмелитесь сказать это как ответ? Я всегда нахожу, что не слишком хорошо умею рационализировать UB.   -  person Bathsheba    schedule 23.02.2017
comment
Добавление в комментарий @Olaf для GCC -Wshadow.   -  person Iharob Al Asimi    schedule 23.02.2017
comment
@bathsheba Я чувствую, что это будет хорошо в вашем ответе. У вас есть мое разрешение использовать это дословно, если хотите.   -  person Mad Physicist    schedule 23.02.2017
comment
@MadPhysicist: Я бы предпочел, чтобы вы скопировали мои баллы в ответ, который вы пишете. Не стесняйтесь, без указания авторства.   -  person Bathsheba    schedule 23.02.2017
comment
@MadPhysicist: Я добавил это в конце. Наличие +9 на такой однозначный ответ задело мою совесть. Плюс кот снова получает это.   -  person Bathsheba    schedule 23.02.2017
comment
Скомпилируйте со всеми предупреждениями и отладочной информацией (gcc -Wall -Wextra -g). Затем используйте отладчик (gdb) для пошагового выполнения вашей программы.   -  person Basile Starynkevitch    schedule 23.02.2017
comment
void main недействителен C.   -  person David Conrad    schedule 24.02.2017


Ответы (5)


Поведение вашей программы не определено.

Внутренняя область видимости i не инициализирована в момент чтения.

(Что может случиться, так это то, что повторно введенный i на последующих итерациях занимает ту же самую память, что и предыдущее воплощение внутреннего i, а неинициализированная память на первой итерации соответствует 0. Но don не полагайтесь на это. В других случаях компилятор может съесть вашу кошку.)

person Bathsheba    schedule 23.02.2017
comment
Нет, int может содержать представление ловушки. Если бы это было unsigned char, то вы были бы правы. - person Bathsheba; 23.02.2017
comment
@Bathsheba, это было бы даже неправильно. У него нет адреса, поэтому его поведение не определено. И если бы у него был занят его адрес, он имел бы неопределенное значение (даже если бы беззнаковый символ), и использование его в библиотечной функции имело бы неопределенное поведение. - person Antti Haapala; 23.02.2017
comment
Это всегда UB согласно этому. - person Lundin; 23.02.2017
comment
На большинстве платформ (если вы считаете развернутые процессоры или количество отдельных архитектур) int не может содержать представление ловушки. Конечно, все еще неопределенное поведение. - person hyde; 23.02.2017
comment
Это всегда УБ. Если бы тип был unsigned char, тогда не было бы представления ловушки. - person Jonathan Leffler; 23.02.2017
comment
Избивая мертвую лошадь, я уже связал ответ, в котором говорится, почему это всегда UB. Представление ловушки или ее отсутствие не имеет значения. Как говорит Антти, это потому, что это автоматическая переменная, адрес которой не указан. C11 6.3.2.1. - person Lundin; 23.02.2017
comment
В других случаях компилятор может съесть вашу кошку. Да ладно тебе! Не то дерьмо, компилятор! Ты уже съел 50 моих кошек за последний час. Неудивительно, что за эти годы вы приобрели такой размер установки со всеми этими кошками, которых вы едите. - person Kaiserludi; 23.02.2017
comment
@Lundin: в сообщении, которое вы связали, говорится, что если у объекта есть адрес, а его тип не имеет представлений ловушек, тогда переменная будет содержать значение Unspecified. Такая конструкция позволяет использовать такие конструкции, как struct string15 {char[16] st;} mystring;, а затем внутри функции struct string15 x; strcpy(x.st,"Hello"); mystring = x;, чтобы не тратить время на запись частей x.st, не имеющих отношения к работе программы. В описании отчета о дефектах говорится, что если бы вышеупомянутое сопровождалось _4 _... - person supercat; 23.02.2017
comment
... не будет никакой гарантии, что a==b или что c==0, но любая реализация, которая не может гарантировать такие вещи, должна считаться непригодной для многих целей, включая все, что может иметь какие-либо последствия для безопасности (в то время как связанный с безопасностью код, конечно, должен правильно инициализировать буферы, ему также нужна возможность делать снимок объекта, проверять его и знать, что снимок не изменится самопроизвольно после проверки). Было бы разумно, если бы реализация потребовала использования директив для преобразования неопределенных значений в неопределенные значения ... - person supercat; 23.02.2017
comment
... если такие директивы существовали, но, конечно, Стандарт не определяет их, и любой код, который пытался использовать специфичные для компилятора директивы для этой цели, вероятно, был бы непригоден для реализаций, которые не определяют их, потому что обычное присваивание выполняет то же самое. - person supercat; 23.02.2017
comment
В старые времена comp.lang.c в Usenet поведение такого кода обычно приводило к вылету демонов из вашего носа. - person Laconic Droid; 24.02.2017
comment
@supercat Я считаю, что уже есть отчет о дефекте, касающийся того, могут ли несколько чтений одной и той же переменной с неопределенным значением давать разные значения или нет. open-std.org/jtc1/sc22/wg14/ www / docs / dr_451.htm - person Lundin; 24.02.2017
comment
Не то дерьмо, компилятор! Да ладно тебе! Только не то дерьмо, @Kaiserludi! Вы уже накормили компилятор 50 кошек за последний час. Неудивительно, что вы прошли через так много кошек за эти годы со всеми теми UB, которые вы кодируете. - person philipxy; 24.02.2017
comment
@Lundin: Я прочитал отчет. Есть некоторые области приложения, в которых модель выполнения, описанная в них, может быть полезна, но для многих полей приложения требуются средства преобразования потенциально неопределенного значения в значение, которое в худшем случае является неопределенным, и ни Стандарт, ни общепринятая практика не предлагают никаких средств, с помощью которых это может быть выполнено на компиляторе, который не считает присваивания выполнением такого преобразования. В то время как код, заботящийся о безопасности, должен очищать все буферы перед использованием, высокопроизводительный код, который не должен заботиться о безопасности, не должен ... - person supercat; 24.02.2017
comment
... предварительная очистка памяти в случаях, когда любой ранее существовавший битовый шаблон будет работать одинаково хорошо. Агрессивные оптимизации, мотивирующие DR, будут заблокированы кодом, который предварительно очищал буферы, поэтому такие оптимизации могут иметь значение только в том случае, если они позволяют коду иметь полезную семантику без предварительной очистки. Я бы хотел, чтобы Комитет по стандартам напомнил разработчикам компиляторов, что разрешение поведения - это просто приглашение для авторов компилятора вынести суждение относительно того, будет ли такое поведение служить целям их реализации; он не предназначен в качестве замены такого суждения. - person supercat; 24.02.2017
comment
@Lundin: К сожалению, растет разрыв между диалектами C, ориентированными на разные цели. Нет никаких директив для преобразования неопределенных значений в неопределенные, потому что реализации, нацеленные на поля, где такая семантика иногда требуется, предоставляют их по умолчанию, а приложениям в полях, где они не нужны, они не нужны. Если бы такие директивы были определены, код, который полагается на такую ​​семантику без использования директив, мог бы быть устаревшим, что позволило бы унифицировать диалекты. Однако я не видел реальных движений в этом направлении. - person supercat; 24.02.2017

Второй printf в вашей программе выводит значение мусора из неинициализированной локальной переменной i. В общем случае поведение не определено.

Случайно место хранения, которое представляет ваш i (ячейка памяти или регистр ЦП), одинаково на каждой итерации цикла. По другой случайности тело вашего цикла выполняется как единый составной оператор для каждой итерации (в отличие от разворачивания цикла и одновременного выполнения всех итераций с чередованием). По еще одной случайности место хранения i сохраняет свое старое значение с предыдущей итерации. Итак, печатаемый вами мусор соответствует последнему сохраненному значению из этого места на предыдущей итерации цикла.

Вот почему вы видите 5 в локальном i на каждой итерации, кроме первой. На первой итерации это значение мусора оказалось 0.

person AnT    schedule 23.02.2017

Я думаю, что происходит следующее:

Breakpoint 1, main () at abc.c:4
4       int j = 0;
(gdb) s
5       int i = 0;
(gdb)
6       for(j=0;j<5;j++){
(gdb) p &i
$23 = (int *) 0x7fff5fbffc04           //(1) (addr = 0x7fff5fbffc04) i = 0
(gdb) p i
$24 = 0                                              // (2) i == 0
(gdb) s
7           printf("Iteration %d :  %d ",j+1,i);
(gdb) p &i
$25 = (int *) 0x7fff5fbffc00            //(3) here compiler finds there is contention in variable 'i' and assigns the inner one which is in present scope. Now, when subroutines are called, the stack frame is populated with (arguments, return address and local variables) and this is when the inner 'i' was also got allocated in this inner loop block but not initialized yet and this is the reason i get garbage value in the first integration output.

(gdb) p i
$26 = 1606417440                          // (4)  Here i == 1606417440 (Garbage)
(gdb) s
9           printf("%d",i);
(gdb) s
10          i = 5;
(gdb) p &i
$27 = (int *) 0x7fff5fbffc00
(gdb) p i
$28 = 1606417440
(gdb) s
11          printf("\n");
(gdb) p &i
$29 = (int *) 0x7fff5fbffc00
(gdb) p i                                                //(5) after executing previous statement, now i == 5
$30 = 5
(gdb) 
person Milind Deore    schedule 23.02.2017

Компиляторы C имеют некоторую свободу в том, как они реализуют вещи, и нет ничего необычного в том, что локальный "i" фактически не воссоздается на каждом проходе цикла. Вероятно, на самом деле он создается только один раз и повторно используется на каждом проходе. В этом случае ваша первая печать выходит за рамки локальной переменной, поэтому i используется вне цикла. Вторая печать использует локальный i, как он находится в области видимости, и после того, как первая итерация была установлена ​​на 5. На первой итерации правильное значение undefined, а не 0, но поведение undefined зависит от реализации в вашей реализации. он автоматически инициализируется значением 0.

person dlb    schedule 23.02.2017

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

Стандарт C говорят,

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

person msc    schedule 23.02.2017
comment
Я не понимаю, насколько уместен этот абзац .. Он действительно ничего не говорит о UB. - person Eugene Sh.; 23.02.2017
comment
Этот ответ неверен, и цитируемая часть не имеет отношения к делу. См. это. - person Lundin; 24.02.2017