Что значит неинициализированная переменная в c

от admin

Что хранит в себе неинициализированная переменная в C#?

Собственно вопрос и состоит в этом: Что хранить в себе неинициализированная переменная в C#? Насколько я знаю, в С++ такие переменные хранят всякий "мусор", в Java null, а для C# не нашел информации, кроме как "выдаст ошибку"

Неинициализированным переменным присваиваются значения по умолчанию, а именно:

  • для ссылочных типов: null ;
  • для встроенных числовых типов ( int , double и т.п.): 0
  • для bool : false
  • для char : \0
  • для перечислений: значение выражения (E)0 , где E — тип перечисления;
  • для структур: значение, созданное путем установки всем полям значений по умолчанию;
  • для Nullable : экземпляр, свойство HasValue которого имеет значение false , а свойство Value не определено.

Значение по-умолчанию для типа можно определить с помощью оператора default :

Также, начиная с C# 7.1 можно использовать литерал default для присвоения значений переменным:

В языке C# неинициализированнае переменные не хранят в себе ничего, поскольку язык не позволяет читать из таких переменных.

1.6 – Неинициализированные переменные и неопределенное поведение

В отличие от некоторых языков программирования, C/C++ не инициализирует большинство переменных автоматически заданным значением (например, нулем). Таким образом, когда компилятор выделяет переменной место в памяти, значением по умолчанию для этой переменной является любое (мусорное) значение, которое уже находится в этой области памяти! Переменная, которой не было присвоено известное значение (обычно посредством инициализации или присваивания), называется неинициализированной переменной.

Примечание автора

Многие читатели ожидают, что термины «инициализированный» и «неинициализированный» будут строго противоположными, но это не совсем так! Инициализация означает, что объекту было предоставлено начальное значение в точке определения. Неинициализированный означает, что объекту не было присвоено известное значение (каким-либо образом, включая присваивание). Следовательно, объект, который не инициализирован, но которому затем было присвоено значение, больше не является неинициализированным (потому что ему было присвоено известное значение).

  • инициализация = объекту присваивается известное значение в точке определения;
  • присваивание = объекту присваивается известное значение в точке, выходящей за рамки определения;
  • неинициализированный = объекту еще не присвоено известное значение.

В качестве отступления.

Отсутствие инициализации является оптимизацией производительности, унаследованной от C, когда компьютеры были медленными. Представьте себе случай, когда вы собираетесь прочитать 100 000 значений из файла. В таком случае вы можете создать 100 000 переменных, а затем заполнить их данными из файла.

Если бы C++ инициализировал все эти переменные при создании значениями по умолчанию, это привело бы к 100 000 инициализаций (что было бы медленно) и к небольшой выгоде (поскольку вы всё равно перезапишете эти значения).

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

Использование значений неинициализированных переменных может привести к неожиданным результатам. Рассмотрим следующую короткую программу:

В этом случае компьютер выделит для x некоторую неиспользуемую память. Затем он отправит значение, находящееся в этой ячейке памяти, в std::cout , который напечатает значение (интерпретируемое как целое число). Но какое значение он напечатает? Ответ – «кто знает!», и ответ может (или не может) меняться каждый раз, когда вы запускаете программу. Когда автор запускал эту программу в Visual Studio, std::cout в первый раз вывел значение 7177728, а во второй раз – 5277592. Не стесняйтесь компилировать и запускать программу самостоятельно (ваш компьютер не взорвется).

В качестве отступления.

Некоторые компиляторы, такие как Visual Studio, при использовании конфигурации отладочной сборки будут инициализировать содержимое памяти некоторым предустановленным значением. Этого не произойдет при использовании конфигурации сборки выпуска. Поэтому, если вы хотите запустить указанную выше программу самостоятельно, убедитесь, что вы используете конфигурацию сборки выпуска (чтобы вспомнить, как это сделать, смотрите урок «0.9 – Настройка компилятора: конфигурации сборки»). Например, если вы запустите приведенную выше программу в конфигурации отладки в Visual Studio, она будет неизменно печатать -858993460 , потому что с помощью этого значения (интерпретируемого как целое число) Visual Studio инициализирует память в конфигурациях отладки.

Большинство современных компиляторов пытаются определить, используется ли переменная без присваивания значения. Если они смогут это обнаружить, они обычно выдадут ошибку времени компиляции. Например, компиляция приведенной выше программы в Visual Studio выдала следующее предупреждение:

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

Использование неинициализированных переменных – одна из наиболее распространенных ошибок, которые совершают начинающие программисты, и, к сожалению, она также может быть одной из самых сложных для отладки (потому что программа всё равно может работать нормально, если неинициализированное значение было присвоено определенной области памяти, которая содержала приемлемое значение, например, 0).

Это основная причина использования оптимальной практики «всегда инициализировать переменные».

Неопределенное поведение

Использование значения из неинициализированной переменной – наш первый пример неопределенного поведения. Неопределенное поведение – это результат выполнения кода, поведение которого не определено языком C++. В этом случае в языке C++ нет правил, определяющих, что произойдет, если вы используете значение переменной, которой не было присвоено известное значение. Следовательно, если вы действительно сделаете это, результатом будет неопределенное поведение.

Код, реализующий неопределенное поведение, может проявлять любые из следующих симптомов:

  • ваша программа при каждом запуске дает разные результаты;
  • ваша программа постоянно дает один и тот же неверный результат;
  • ваша программа ведет себя непоследовательно (иногда дает правильный результат, иногда нет);
  • кажется, что ваша программа работает, но позже выдает неверные результаты;
  • ваша программа вылетает сразу после запуска или позже;
  • ваша программа работает с одними компиляторами, но не работает с другими;
  • ваша программа работает до тех пор, пока вы не измените какой-нибудь другой, казалось бы, несвязанный код.

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

C++ содержит множество случаев, которые могут привести к неопределенному поведению, если вы не будете осторожны. Мы будем указывать на них в будущих уроках всякий раз, когда с ними столкнемся. Обратите внимание на эти случаи и убедитесь, что вы их избегаете.

Правило

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

Примечание автора

Один из наиболее распространенных типов комментариев, которые мы получаем от читателей, гласит: «Вы сказали, что я не могу делать X, но я всё равно сделал это, и моя программа работает! Почему?".

Есть два общих ответа. Наиболее распространенный ответ заключается в том, что ваша программа на самом деле демонстрирует неопределенное поведение, но это неопределенное поведение в любом случае дает желаемый результат… пока. Завтра (или на другом компиляторе или машине) этого может и не быть.

В качестве альтернативы, иногда авторы компиляторов допускают вольность к требованиям языка, когда эти требования могут быть более строгими, чем необходимо. Например, в стандарте может быть сказано: «Вы должны сделать X перед Y», но автор компилятора может счесть это ненужным и заставить Y работать, даже если вы сначала не выполните X. Это не должно влиять на работу правильно написанных программ, но в любом случае может привести к тому, что неправильно написанные программы будут работать. Таким образом, альтернативный ответ на вышеупомянутый вопрос заключается в том, что ваш компилятор может просто не следовать стандарту! Такое случается. Вы можете избежать этого, если отключили расширения компилятора, как описано в уроке «0.10 – Настройка компилятора: расширения компилятора».

Небольшой тест

Вопрос 1

Что такое неинициализированная переменная? Почему вам следует избегать их использования?

Неинициализированная переменная – это переменная, которой программа не присвоила значение (обычно посредством инициализации или присваивания). Использование значения, хранящегося в неинициализированной переменной, приведет к неопределенному поведению.

Вопрос 2

Что такое неопределенное поведение и что может произойти, если вы сделаете что-то, что демонстрирует неопределенное поведение?

Неопределенное поведение – это результат выполнения кода, поведение которого не определяется языком. Результатом может быть что угодно, в том числе и то, что ведет себя правильно.

C2017/Неопределённое поведение

Неопределённое поведение (undefined behaviour, UB) — свойство некоторых языков программирования (наиболее заметно в C), программных библиотек и аппаратного обеспечения в определённых маргинальных ситуациях выдавать результат, зависящий от реализации компилятора (библиотеки, микросхемы) и случайных факторов наподобие состояния памяти.

Спецификация не определяет поведение языка в любых возможных ситуациях, а говорит: «при условии А результат операции Б не определён». Допускать такую ситуацию в программе считается ошибкой; даже если на некотором компиляторе программа успешно выполняется, она не будет кроссплатформенной и может отказать на другой машине, в другой ОС или при других настройках компилятора.

При UB результат выполнения программы может быть любым: она аварийно завершиться, может отформатировать вам диск…

Причины

Все знают, что C — это низкоуровневый язык. Все знают, что это не переносимый язык (это не Java и не JavaScript). Однако есть непонимание места C (и стандартов ANSI C и ISO C) в этом мире.

Язык C был создан для главной цели — для написания операционной системы UNIX. То есть для низкоуровневого кода. Однако важно, что UNIX — не только многозадачная и многопользовательская система, но она ещё и переносимая.

Действительно: как можно сделать какую-то программу переносимой? Очевидный вариант: написать её на переносимом языке, в описании которого подробно описать всё, что делают разные операторы, как обрабатывается переполнение, обращение к неинициализированному указателю и прочее, прочее. В коде появятся всевозможные проверки, и многие места станут неэффективными на ровном месте (на той или иной архитектуре будет получаться неэффективный код). Не самый лучший вариант для ядра OS. Да и вообще — что это за «низкоуровневый язык», где простейшие вещи транслируются в 10 команд ассемблера (и хорошо если в 10, а не в 100)?

Разработчики UNIX (и C) пошли другим путём. Язык они сделали непереносимым, а вот написанный на нём UNIX — очень даже переносимым. Как этого добились? Запретами. Сдвиг на сильно большие величины на разных процессорах даёт разные результаты? Значит, использовать такие сдвиги в программе нельзя. Один процессор при переполнении выбрасывает исключения, а другой «тихо» порождает отрицательное число? Значит использовать такую конструкцию в программе нельзя. И так далее. Таких правил, призванных обеспечить переносимость кода накопились десятки и сотни.

Заметьте: эти правила — ориентированы на программиста, ни в коем случае не на разработчика компилятора. Это программисту нужен переносимый код, не компилятору. А для компилятора — эти правила, наоборот, дают простор для реализации. Если у нас сложение порождает отрицательные числа, а вычитание — бросает исключение, то что нам делать? Да неважно: программист же должен всё равно позаботится о том, чтобы такого не происходило.

Именно это приводит к тому, что UNIX (и его «идейный наследник» Linux) поддерживают совершенно невероятное число платформ. И многие программы, написанные на этом непереносимом языке, тоже работают в куда большем числе мест, чем программы написанные «на истинно переносимых языках» типа С# или Java. Дело в том, что переносимость в случае использования C# или Java возлагается в первую очередь на компилятор, а вот в C — на программиста. И если программист всё делает аккуратно, то программа на C будет работать.

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

В чём плюсы UB

Только тем, что упрощает работу компилятора и позволяет ему генерировать очень эффективный код.

В чём минусы UB

Обычно нельзя доверять программисту, что в его коде нет UB. Иногда гарантии надёжности важнее небольшого прироста скорости (особенно если данные приходят извне от непроверенных источников, например в веб-сервере или браузере).

Примеры

Чтение из неинициализированных переменных

Локальные переменные на стеке по умолчанию не инициализированы. Память, которую выделяет malloc, также не инициализирована.

Принцип: вы не платите за то, что не используете. Если вам нужны нули, явно их задавайте. Компилятор не тратит на зануление лишние инструкции.

Переполнение знаковых целых типов

Не гарантируется, что INT_MAX+1 даст INT_MIN.

согласно стандарту может напечатать

или отформатировать жёсткий диск. И не важно, что вы запускаете это на x86, где для хранения отрицательных чисел используется дополнительный код, а инструкция ADD одна и та же для знаковых и беззнаковых. Стандарт ничего не гарантирует.

Читать:
Как сильно сожмется mpeg4 через архиватор 7zip

Это даёт основание для многих оптимизаций.

  • x + 1 > x всегда истинно,
  • x * 2 / 2 всегда равно x,
  • цикл вида for (i = 0; i <= N; ++i) всегда конечен и делает N+1 итерацию, и не нужно рассматривать частные случаи типа N == INT_MAX.

Для беззнаковых целых гарантируется переполнение "с заворотом".

Битовые сдвиги на недопустимые значения

Сдвиги uint32_t на 32 и более бит — неопределённое поведение.

Например, процессор x86 при сдвигах 32-битного числа от величины сдвига берёт только младшие пять бит, поэтому сдвиг на 32 эквивалентен сдвигу на 0.

Разыменование нулевого указателя

Это неопределённое поведение.

  • Не гарантируется, что программа упадёт.
  • Если на какой-то платформе удастся разместить данные в памяти с нулевого адреса, то не гарантируется, что вы получите к ним доступ.

Макрос NULL по-разному определяется в C и C++.

Стандарт гарантирует, что никакой указатель на переменную или функцию не будет равен NULL.

Разыменование некорректного указателя

  • Неинициализированный указатель
  • Указатель на освобождённую с помощью free память
  • Указатель на освобождённую стековую память
  • Выход за границы массива
  • В частности, разыменование указателя, полученного через malloc(0)
  • Адресная арифметика за пределами массива

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

strict aliasing

Явление aliasing — когда на один и тот же участок памяти указывают несколько разных указателей.

У переменной a здесь целых три имени (alias'а), это совершенно нормальный и корректный код.

Оптимизации и restrict

Компилятор обязан понимать и помнить про aliasing не только в таком наглядном случае, но и там, где человек неявно никаких алиасов не предполагает. Например:

Компилятору никто не дал гарантий, что out-переменная по адресу *out решительно не пересекается с in-данными по адресу *in. Поэтому на каждой итерации внутреннего цикла происходит запись *out обратно в память, даже при максимальном уровне оптимизации.

Ключевое слово restrict позволяет программисту сообщить компилятору, что в течение времени жизни указателя только он сам и непосредственно полученные из него указатели (например "указатель + 1") будут указывать на конкретный блок памяти.

Гарантию того, что на один блок памяти не будет указывать более одного указателя, даёт программист. При этом оптимизирующий компилятор может генерировать более эффективный код.

Видно, что сохранение в память вынесено из цикла.

Замеры показывают, что код ускоряется примерно в 1,5 раза.

Правило strict aliasing

Как видим, устранение алиасинга может вылиться в неплохое улучшение скорости. Из этих соображений в стандарте C99, а следом в C++, ввели "strict aliasing rule".

В режиме strict aliasing компилятор считает, что объекты, на которые показывают указатели «существенно различных» типов, не могут храниться в одном и том же участке памяти, и может использовать это при оптимизациях.

Когда правило не действует?

  • Для указателей типов char* (включая знаковый и беззнаковый). Они могут указывать куда угодно в памяти.
  • Указатели на знаковый и соответствующий беззнаковый тип не считаются существенно различными.
  • Указатели, отличающиеся const-, volatile-квалификаторами, не считаются существенно различными.
  • Допустимо иметь указатель на struct или union и указатель на отдельное поле.
Пример: доступ к битам вещественного числа

Пусть мы хотим реализовать вычисление модуля вещественного числа путём ручного сброса старшего бита.

Этот код не является корректным с точки зрения стандарта C, так как нарушается правило strict aliasing: указатели разных типов int* и float* указывают на одну и ту же память.

Как же добиться нужного эффекта правильно?

Первый способ. Использовать указатели типа char.

Второй способ. Использовать memcpy. Эта функция принимает указатели void*, это разрешает aliasing.

Третий способ. Использовать union. Это допускается в соответствии со стандартом C99.

Поддержка в компиляторах

В gcc и clang есть возможность отключить strict aliasing rule ключом -fno-strict-aliasing.

Компилятор MSVC не рассчиывает на это правило.

Точки следования

Точка следования (англ. sequence point) — любая точка программы, в которой гарантируется, что все побочные эффекты предыдущих вычислений уже проявились, а побочные эффекты последующих ещё отсутствуют.

Модификация объекта между двумя точками следования более одного раза вызывает UB.

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

Переполнение стека

memcpy

  • Использование memcpy для копирования перекрывающихся областей памяти.
  • memcpy(0, 0, 0): даже если копируется ноль байт, указатели обязаны быть валидными.

Деление на 0

Целочисленное деление. Вещественное деление рассматривали на прошлой паре.

Модификация строкового литерала

Забытый return

В не-void функциях, кроме main().

Бесконечный цикл

Стандарт C объявляет «неопределённым действием» бесконечный цикл без изменения внешнего состояния — поэтому компиляторы C вправе считать любой такой цикл конечным.

В стандарте C11 разрешили бесконечные циклы, где условное выражение является константой.

no newline at end of file

Согласно стандарту, непустой файл обязан заканчиваться переводом строки.

Пример, когда это важно.

В языке C++ в версии C++11 это правило было ликвидировано.

Неуточняемое поведение

Кроме неопределённого поведения, есть ещё два схожих понятия, но менее опасных.

Согласно стандарту языка C99,

  • 3.4.3. неуточняемое поведение (unspecified behavior) — использование неуточняемого значения или иное поведение, где данный Международный стандарт предоставляет два или более варианта и не налагает никаких других требований на выбор в каждом конкретном случае.

Это поведение компьютерной программы, которое может различаться на разных платформах и компиляторах, поскольку спецификация языка программирования предлагает несколько допустимых вариантов реализации некой языковой конструкции. В отличие от неопределённого поведения, программа с неуточняемым поведением с точки зрения соответствия спецификации языка не считается ошибочной. Спецификация обычно ограничивает возможные варианты поведения, хотя и не сводит их в единое допустимое.

Порядок вычисления аргументов функции

В С и C++ (в отличие от языка Java) порядок вычисления параметров функции является неуточняемым; следовательно, в программе, указанной ниже, порядок, в котором будут напечатаны строки «F» и «G», зависит от компилятора.

В каждом конкретном случае компилятор волен выбирать более удобный ему вариант, но всё-таки он должен сначала вычислить их обе, а уж потом результаты сложить.

Поведение, определяемое реализацией

Согласно стандарту языка C99,

  • 3.4.1. поведение, определяемое реализацией (implementation-defined behavior) — неуточняемое поведение, где каждая реализация документирует выбор поведения;

Размеры типов данных

Классическим примером поведения, определяемого реализацией (поведения, которое обязано быть документировано реализациями), является размер типов данных; например long в различных компиляторах и операционных системах может быть размером в 32 или 64 бит.

Преобразование знаковых и беззнаковых чисел

Нельзя просто так взять и преобразовать int в unsigned int. Потому что бывает дополнительный код, обратный код, прямой код. В стандарте было принято решение: разрешить реализации выбрать один из вариантов (иногда явно перечисленных в стандарте, иногда нет), но обязать реализацию всегда использовать один и тот же подход.

Положительные числа, представимые в обоих вариантах при этом гарантированно сохраняются, что здорово облегчает жизнь.

Проблемы кода с UB

Программа, которая содержит undefined behavior, может внезапно перестать работать при смене компилятора или платформы.

Урок №10. Переменные, Инициализация и Присваивание

Программируя на языке C++, мы создаем, обрабатываем и уничтожаем объекты. Объект — это часть памяти, которая может хранить значение. В качестве аналогии мы можем использовать почтовый ящик, куда мы помещаем информацию и откуда её извлекаем. Все компьютеры имеют оперативную память, которую используют программы. При создании объекта, часть оперативной памяти выделяется для этого объекта. Большинство объектов, с которыми мы будем работать в языке C++, являются переменными.

Переменные

Cтейтмент a = 8; выглядит довольно простым: мы присваиваем значение 8 переменной a . Но что такое a ? a — это переменная, объект с именем.

На этом уроке мы рассмотрим только целочисленные переменные. Целое число — это число, которое можно записать без дроби, например: -11, -2, 0, 5 или 34.

Для создания переменной используется стейтмент объявления (разницу между объявлением и определением переменной мы рассмотрим несколько позже). Вот пример объявления целочисленной переменной a (которая может содержать только целые числа):

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

Одной из наиболее распространенных операций с переменными является операция присваивания, например:

Когда процессор выполняет эту инструкцию, он понимает её как «поместить значение 8 в ячейку памяти под номером 150».

Затем мы сможем вывести это значение на экран с помощью std::cout:

l-values и r-values

В языке C++ все переменные являются l-values. l-value (в переводе «л-значение», произносится как «ел-валью») — это значение, которое имеет свой собственный адрес в памяти. Поскольку все переменные имеют адреса, то они все являются l-values (например, переменные a , b , c — все они являются l-values). l от слова «left», так как только значения l-values могут находиться в левой стороне в операциях присваивания (в противном случае, мы получим ошибку). Например, стейтмент 9 = 10; вызовет ошибку компилятора, так как 9 не является l-value. Число 9 не имеет своего адреса в памяти и, таким образом, мы ничего не можем ему присвоить (9 = 9 и ничего здесь не изменить).

Противоположностью l-value является r-value (в переводе «р-значение», произносится как «ер-валью»). r-value — это значение, которое не имеет постоянного адреса в памяти. Примерами могут быть единичные числа (например, 7 , которое имеет значение 7 ) или выражения (например, 3 + х , которое имеет значение х плюс 3 ).

Вот несколько примеров операций присваивания с использованием r-values:

Давайте детально рассмотрим последнюю операцию присваивания:

Здесь переменная b используется в двух различных контекстах. Слева b используется как l-value (переменная с адресом в памяти), а справа b используется как r-value и имеет отдельное значение (в данном случае, 12 ). При выполнении этого стейтмента, компилятор видит следующее:

И здесь уже понятно, какое значение присваивается переменной b .

Сильно беспокоиться о l-values или r-values сейчас не нужно, так как мы еще вернемся к этой теме на следующих уроках. Всё, что вам нужно сейчас запомнить — это то, что в левой стороне операции присваивания всегда должно находиться l-value (которое имеет свой собственный адрес в памяти), а в правой стороне операции присваивания — r-value (которое имеет какое-то значение).

Инициализация vs. Присваивание

В языке C++ есть две похожие концепции, которые новички часто путают: присваивание и инициализация.

После объявления переменной, ей можно присвоить значение с помощью оператора присваивания (знак равенства = ):

В языке C++ вы можете объявить переменную и присвоить ей значение одновременно. Это называется инициализацией (или «определением»).

Переменная может быть инициализирована только после операции объявления.

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

Правило: Если у вас изначально имеется значение для переменной, то используйте инициализацию, вместо присваивания.

Неинициализированные переменные

В отличие от других языков программирования, языки Cи и C++ не инициализируют переменные определенными значениями (например, нулем) по умолчанию. Поэтому, при создании переменной, ей присваивается ячейка в памяти, в которой уже может находиться какой-нибудь мусор! Переменная без значения (со стороны программиста или пользователя) называется неинициализированной переменной.

Использование неинициализированных переменных может привести к ошибкам, например:

В этом случае компилятор присваивает переменной a ячейку в памяти, которая в данный момент свободна (не используется). Затем значение переменной a отправляется на вывод. Но что мы увидим на экране? Ничего, так как компилятор это не пропустит — выведется ошибка, что переменная a является неинициализированной. В более старых версиях Visual Studio компилятор вообще мог бы вывести какое-то некорректное значение (например, 7177728 , т.е. мусор), которое было бы содержимым той ячейки памяти, которую он присвоил нашей переменной.

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

Хорошей практикой считается всегда инициализировать свои переменные. Это будет гарантией того, что ваша переменная всегда имеет определенное значение и вы не получите ошибку от компилятора.

Правило: Убедитесь, что все ваши переменные в программе имеют значения (либо через инициализацию, либо через операцию присваивания).

Похожие статьи