Должна ли инициализация локальной переменной быть обязательной? - PullRequest
11 голосов
/ 26 сентября 2008

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

В c легко показать, что избыточная инициализация оптимизирована:

$ less test.c
#include <stdio.h>
main()
{
#ifdef INIT_LOC
    int a = 33;
    int b;
    memset(&b,66,sizeof(b));
#else
    int a;
    int b;
#endif
    a = 0;
    b = 0;
    printf ("a = %i, b = %i\n", a, b);
}

$ gcc --version
gcc (GCC) 3.4.4 (cygming special, gdc 0.12, using dmd 0.125)

[Не оптимизировано:]

$ gcc test.c -S -o no_init.s; gcc test.c -S -D INIT_LOC=1 -o init.s; diff no_in
it.s init.s
22a23,28
>       movl    $33, -4(%ebp)
>       movl    $4, 8(%esp)
>       movl    $66, 4(%esp)
>       leal    -8(%ebp), %eax
>       movl    %eax, (%esp)
>       call    _memset
33a40
>       .def    _memset;        .scl    3;      .type   32;     .endef

[Оптимизированная:]

$ gcc test.c -O -S -o no_init.s; gcc test.c -O -S -D INIT_LOC=1 -o init.s; diff
 no_init.s init.s
$

Таким образом, производительность WRT при каких обстоятельствах обязательная инициализация переменной НЕ является хорошей идеей?

ЕСЛИ применимо, нет необходимости ограничивать ответы на c / c ++, но, пожалуйста, будьте внимательны с языком / средой (и воспроизводимые доказательства гораздо предпочтительнее предположений!)

Ответы [ 17 ]

11 голосов
/ 26 сентября 2008

Краткий ответ: объявите переменную как можно ближе к первому использованию и инициализируйте на «ноль», если вам все еще нужно.

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

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

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

6 голосов
/ 26 сентября 2008

Если вы считаете, что инициализация избыточна, это так. Моя цель - написать код, максимально понятный человеку. Ненужная инициализация смущает будущего читателя.

Компиляторы C хорошо справляются с отслеживанием использования унифицированных переменных, поэтому опасность этого теперь минимальна.

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

5 голосов
/ 26 сентября 2008

Это отличный пример Преждевременная оптимизация - корень всего зла

Полная цитата:

Нет сомнений в том, что Грааль эффективности ведет к злоупотреблениям. Программисты тратят огромное количество времени на размышления или беспокойство по поводу скорости некритических частей своих программ, и эти попытки повышения эффективности действительно оказывают сильное негативное влияние, когда рассматриваются отладка и обслуживание . Мы должны забыть о малой эффективности, скажем, в 97% случаев: преждевременная оптимизация - корень всего зла. Однако мы не должны упускать наши возможности в этих критических 3%. Такие рассуждения не приведут к самоуспокоенности хорошего программиста, он посмотрит внимательно на критический код; но только после того, как этот код был идентифицирован.

Это пришло от Дональда Кнута . кому ты собираешься верить ... твоим коллегам или Кнуту?
Я знаю, где мои деньги ...

Возвращаясь к первоначальному вопросу: «Должны ли мы ОБЯЗАТЕЛЬНО инициализировать?»
Я бы сказал так:

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

4 голосов
/ 26 сентября 2008

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

3 голосов
/ 26 сентября 2008

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

struct stat s;
s.st_dev = -1;
s.st_ino = -1;
s.st_mode = S_IRWXU;
s.st_nlink = 0;
s.st_size = 0;
// etc...
s.st_st_ctime = -1;
if(stat(path, &s) != 0) {
   // handle error
   return;
}

ВТФ ???

Обратите внимание, что мы исправляем ошибку сразу, поэтому не возникает вопросов о том, что произойдет, если статистика потерпит неудачу.

3 голосов
/ 30 сентября 2008

Позвольте мне рассказать вам историю о продукте, над которым я работал в 1992 году, и позже, для целей этой истории мы назовем Stackrobat. Мне назначили ошибку, которая вызвала сбой приложения на Mac, но не на Windows, о, и ошибка не была надежно воспроизводимой. QA потребовалась лучшая часть недели, чтобы придумать рецепт, который сработал, может быть, 1 в 10 раз.

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

В конце концов, я отследил это, написав собственный профилировщик кода для компилятора. Компилятор с удовольствием вставит вызовы глобальных функций prof_begin () и prof_end (), и вы сможете сами их реализовать. Я написал профилировщик, который взял адрес возврата из стека, нашел инструкцию по созданию фрейма стека, разместил блок в стеке, который представлял локальные объекты для функции, и покрыл их вкусным слоем дерьма, который мог вызвать ошибку шины, если таковая имеется элемент был разыменован.

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

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

Я потратил больше двух недель, пытаясь найти эту ошибку.

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

Если вы не работаете в крошечной системе (встроенной и т. Д.), Инициализация локальных систем должна быть почти бесплатной. Инструкции MOVE / LOAD очень и очень быстрые. Сначала напишите код, чтобы он был надежным и понятным. Рефакторинг, чтобы быть вторым.

2 голосов
/ 26 сентября 2008

Это относится только к C ++, но между этими двумя методами есть определенное различие. Предположим, у вас есть класс MyStuff, и вы хотите инициализировать его другим классом. Вы можете сделать что-то вроде:

// Initialize MyStuff instance y
// ...
MyStuff x = y;
// ...

Что это на самом деле делает, так это вызывает конструктор копирования x. Это так же, как:

MyStuff x(y);

Это отличается от этого кода:

MyStuff x; // This calls the MyStuff default constructor.
x = y; // This calls the MyStuff assignment operator.

Конечно, при конструировании копии и конструировании по умолчанию + присвоении вызывается совершенно другой код. Кроме того, один вызов конструктора копирования, вероятно, будет более эффективным, чем создание с последующим присваиванием.

2 голосов
/ 26 сентября 2008

Иногда вам нужна переменная в качестве заполнителя (например, с использованием функций ftime), поэтому не имеет смысла их инициализировать перед вызовом функции инициализации.

Однако было бы неплохо, на мой взгляд, комментировать тот факт, что вы знаете о подводных камнях, что-то вроде

uninitialized time_t t;
time( &t );
1 голос
/ 26 сентября 2008

В C / C ++ я с тобой полностью согласен.

В Perl, когда я создаю переменную, ей автоматически присваивается значение по умолчанию.

my ($val1, $val2, $val3, $val4);
print $val1, "\n";
print $val1 + 1, "\n";
print $val2 + 2, "\n";
print $val3 = $val3 . 'Hello, SO!', "\n";
print ++$val4 +4, "\n";

Все они изначально настроены на undef. Undef является ложным значением и заполнителем. Из-за динамической типизации, если я добавляю к ней число, это предполагает, что моя переменная является числом и заменяет undef на значение eqivilent false 0. Если я выполняю строковые операции, ложная версия строки является пустой строкой, и это автоматически подставляется.

[jeremy@localhost Code]$ ./undef.pl

1
2
Hello, SO!
5

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

 my($x, $y, $z);

: -)

 my $x = 0;
 my $y = 0;
 my $z = 0;
1 голос
/ 26 сентября 2008

Производительность? Настоящее время? Возможно, когда процессоры работали на частоте 10 МГц, это имело смысл, но сегодня это вряд ли проблема. Всегда инициализируйте их.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...