Насколько важна инициализация переменной

9

Насколько важно инициализировать переменные?

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

Вивек
источник
14
Это зависит от языка. В некоторых языках очень важно предотвращать ошибки, в остальном это просто хорошая вещь, чтобы улучшить читаемость.
Теластин
Спасибо Теластин за ваш вклад. Можете ли вы поставить случай, когда это становится важным в зависимости от языка?
Вивек
4
C ++ является печально известным здесь. В отладке локальные переменные инициализируются равными 0 (или null) обычными компиляторами, но являются случайным мусором при компиляции для выпуска. (хотя мои знания C ++
получены
Это случай, когда однажды сгорел, дважды смирился. Поскольку я видел / имел ошибки, вызванные неинициализированными переменными, особенно указателями, это стало привычкой. Для производительности это обычно не имеет значения. Для утечек памяти, на самом деле не проблема.
Майк Данлавей,
1
@Telastyn это хуже, чем это. Неопределенное поведение не ограничивается состоянием мусора, все может произойти. Компилятор может предположить, что пути, которые читают неинициализированные переменные, недостижимы, и устранить «несвязанные» эффекты, возникающие на этом пути.
Caleth

Ответы:

7

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

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

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

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

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

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

Редактировать: В некоторых языках (например, C #) невозможно использовать неинициализированные переменные, так как программа не будет компилироваться или сообщать об ошибке при выполнении, если это сделано. Однако многие языки с этими характеристиками имеют интерфейсы для потенциально небезопасного кода, поэтому следует соблюдать осторожность при использовании таких интерфейсов, чтобы не вводить неинициализированные переменные.

mattnz
источник
6
Многие языки программирования автоматически устанавливают для своих переменных какое-то предопределенное значение, поэтому многое из того, что вы здесь говорите, неприменимо к этим языкам.
Роберт Харви
2
Просто чтобы повторить то, что сказал @RobertHarvey, ничего из этого не применимо к C #. Нет никакого преимущества в производительности для инициализации ваших переменных при их объявлении, и невозможно использовать неинициализированную переменную, поэтому вы не можете винить в этом невоспроизводимые ошибки. (Это есть возможность использовать поле неинициализированного класса, но он получает набор значения по умолчанию , и генерирует предупреждение в этом случае)
Bobson
4
@mattnz - Дело в том, что для языков, которые ведут себя как C # (или Java), некоторые из этих советов вводят в заблуждение или прямо неверны. Как вопрос, не зависящий от языка, он должен иметь ответ, не зависящий от языка, что означает обращение к языкам, которые действительно обрабатывают инициализированные переменные так же, как и те, которые этого не делают.
Бобсон
1
Я бы также добавил, что проблему с неинициализированными переменными нетрудно найти, так как любой наполовину достойный компилятор / статический анализатор предупредит о них
jk.
1
Для Java (и, вероятно, C #) преждевременная инициализация локальных файлов не нужна и, возможно, приводит к большему количеству ошибок. Например, установка переменной в null перед ее условным назначением лишает компилятора возможности сообщать вам, что один из путей через код может не привести к назначению переменной.
JimmyJames
7

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

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

Kevin
источник
6

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

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

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

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

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

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

Карл Билефельдт
источник
Всегда? Вы имеете в виду, что «всегда», как в «Как фиксированное сообщение Valgrind делает OpenSSL рядом с бесполезным» marc.info/?t=114651088900003&r=1&w=2 ? Или вы имеете в виду другого, почти всегда?
JensG
1
Я могу вспомнить три языка, которые допускают неинициализированные переменные без ошибок, один из которых использует их в лингвистических целях.
DougM
Я был бы заинтересован в специфике. Я подозреваю, что в этих случаях переменные не являются действительно неинициализированными, а инициализируются не так, как непосредственно программистом на сайте декларации. Или они назначаются какими-то косвенными средствами перед разыменованием.
Карл Билефельдт
5

Инициализация, подразумевает, что начальное значение имеет значение. Если начальное значение имеет значение, тогда да, ясно, что вы должны убедиться, что оно инициализировано. Если это не имеет значения, это означает, что он будет инициализирован позже.

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

Утечки памяти - это совершенно другая проблема, которая обычно включает в себя функцию выделения памяти для выдачи и последующей переработки блоков памяти. Подумайте о почтовом отделении. Вы идете и просите почтовый ящик. Они дают вам один. Вы просите еще один. Они дают вам еще один. Правило заключается в том, что когда вы используете почтовый ящик, вам нужно его вернуть. Если вы забудете вернуть его, они все еще думают, что он у вас есть, и никто больше не сможет его использовать. Таким образом, часть памяти связана и не используется, и это называется утечкой памяти. Если в какой-то момент вы продолжите запрашивать ящики, вам не хватит памяти. Я упростил это, но это основная идея.

Эллиптический вид
источник
-1 вы переопределяете, что означает инициализация в этом контексте.
Питер Б
@ Питер Б, я не понимаю твой комментарий. Пожалуйста, если хотите, скажите, как я, «переосмысливая, что означает инициализация в этом контексте». Спасибо
эллиптический вид
Прочитайте собственное предложение, это круговое рассуждение: «Инициализация, подразумевает, что начальное значение имеет значение. Если начальное значение имеет значение, тогда да, ясно, что вы должны убедиться, что оно инициализировано. Если это не имеет значения, это означает, что оно получит инициализирован позже. "
Питер Б
@Pieter B, Некоторые люди инициализируют как общее правило, а не по программной причине, т.е. они инициализируют, имеет ли значение начальное значение или нет. Разве это не сердце OQ: насколько важно инициализировать переменную? Во всяком случае, вы были проголосовали здесь.
эллиптический вид
2

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

Константы и переменные класса

Переменные класса, отмеченные staticв Java, похожи на константы. Эти переменные обычно должны быть окончательными и инициализироваться непосредственно после определения с использованием =или из блока инициализатора класса static { // initialize here }.

поля

Как и во многих языках более высокого уровня и сценариев, полям будет автоматически назначаться значение по умолчанию. Для цифр charэто и будет нулевое значение. Для строк и других объектов это будет null. Теперь nullэто опасно и должно использоваться экономно. Таким образом, эти поля должны быть установлены в допустимое значение как можно скорее. Конструктор обычно идеальное место для этого. Чтобы убедиться, что переменные установлены во время конструктора, а не изменены впоследствии, вы можете пометить их finalключевым словом.

Попробуйте не поддаваться искушению использовать в nullкачестве какого-либо флага или особого значения. Например, лучше включить определенное поле для хранения состояния. Поле с именем, stateкоторое использует значения Stateперечисления, будет хорошим выбором.

Параметры метода

Поскольку изменения значений параметров (будь то ссылки на объекты или базовые типы, такие как целые числа и т. Д.) Не будут видны вызывающей стороне, параметры должны быть помечены как final. Это означает, что значения самой переменной нельзя изменить. Обратите внимание, что значение экземпляров изменяемого объекта может быть изменено, ссылка не может быть изменена, чтобы указывать на другой объект или nullхотя.

Локальные переменные

Локальные переменные не инициализируются автоматически; они должны быть инициализированы перед использованием их значения. Один из способов убедиться в том, что ваша переменная инициализирована, состоит в том, чтобы напрямую инициализировать их каким-либо значением по умолчанию. Это то, что вы не должны делать. В большинстве случаев значение по умолчанию не является ожидаемым.

Гораздо лучше определять переменную только там, где она нужна. Если переменная должна принимать только одно значение (что верно для большинства переменных в хорошем коде), вы можете пометить переменную final. Это гарантирует, что локальная переменная назначается ровно один раз, а не ноль раз или два раза. Пример:

public static doMethod(final int x) {
    final int y; // no assignment yet, it's final so it *must* be assigned
    if (x < 0) {
        y = 0;
    } else if (x > 0) {
        y = x;
    } else {
        // do nothing <- error, y not assigned if x = 0
        // throwing an exception here is acceptable though
    }
}

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

Мартен Бодьюс
источник
1

Нет проблем с неинициализацией переменных.

Проблема только в том, что вы читаете переменную, которая еще не была записана.

В зависимости от компилятора и / или типа переменной инициализация выполняется при запуске приложения. Или нет.

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

mouviciel
источник
0

Инициализация переменных (неявно или явно) имеет решающее значение. Не инициализация переменной всегда является ошибкой (однако они могут быть инициализированы неявно. См. Ниже). Современные компиляторы, такие как компилятор C # (в качестве примера), воспринимают это как ошибку и не позволяют выполнять код. Неинициализированная переменная просто бесполезна и вредна. Если вы не создаете генератор случайных чисел, вы ожидаете, что от фрагмента кода получится детерминированный и воспроизводимый результат. Этого можно достичь, только если вы начнете работать с инициализированными переменными.

Действительно интересный вопрос заключается в том, инициализируется ли переменная автоматически или вам нужно делать это вручную. Это зависит от используемого языка. Например, в C # поля, то есть «переменные» на уровне класса, всегда автоматически инициализируются значением по умолчанию для этого типа переменной default(T). Это значение соответствует битовой комбинации, состоящей из всех нулей. Это часть спецификации языка, а не просто техническая деталь реализации языка. Поэтому вы можете смело на это рассчитывать. Безопасно не инициализировать переменную явно, если (и только если) спецификация языка утверждает, что она инициализируется неявно.Если вы хотите другое значение, вы должны явно инициализировать переменную. Однако; в C # локальные переменные, то есть переменные, объявленные в методах, не инициализируются автоматически, и вы всегда должны инициализировать переменную явно.

Оливье Жако-Дескомб
источник
2
это не специфический для C # вопрос.
DougM
@ DougM: я знаю. Это не специфичный для C # ответ, я просто взял C # в качестве примера.
Оливье Жако-Дескомб
Не все языки требуют явной инициализации переменных. Ваше утверждение «не инициализация всегда является ошибкой» является ложным и не добавляет ясности к рассматриваемому вопросу. Вы можете пересмотреть свой ответ.
DougM
@DougM: Вы присматривали за моим предложением «Действительно интересный вопрос: автоматически ли инициализируется переменная или нужно ли делать это вручную»?
Оливье Жако-Дескомб
Вы имеете в виду тот, который похоронен на полпути в середине абзаца? да. Вы должны были сделать это более заметным и добавить квалификатор к своему требованию «всегда».
DougM