Это хорошая практика для инициализации массива в C/C++?
Недавно я столкнулся со случаем, когда мне нужно сравнить два файла (золотой и ожидаемый) для проверки результатов теста, и хотя данные, записанные в оба файла, были одинаковыми, файлы не совпадают.
При дальнейшем исследовании я обнаружил, что существует структура, которая содержит несколько целых чисел и массив символов из 64 байтов, и не все байты массива символов используются в большинстве случаев, а неиспользуемые поля из массива содержат случайные данные, и что был причиной несоответствия.
Это заставило меня задать вопрос, является ли хорошей практикой инициализация массива также в C/C++, как это делается в Java?
8 ответов
Хорошей практикой является инициализация памяти / переменных перед их использованием - неинициализированные переменные являются большим источником ошибок, которые зачастую очень трудно отследить.
Инициализация всех данных - очень хорошая идея при записи их в формат файла: он обеспечивает чистоту содержимого файла, поэтому с ним легче работать, и он менее подвержен проблемам, если кто-то неправильно пытается "использовать" неинициализированные данные (помните, что это может не только ваш собственный код, который читает данные в будущем), и делает файлы гораздо более сжимаемыми.
Единственная веская причина не инициализировать переменные перед их использованием - это критические для производительности ситуации, когда инициализация технически "ненужна" и требует значительных накладных расходов. Но в большинстве случаев инициализация переменных не принесет существенного вреда (особенно если они объявлены только непосредственно перед их использованием), но сэкономит вам много времени на разработку за счет устранения общего источника ошибок.
Использование неопределенного значения в массиве приводит к неопределенному поведению. Таким образом, программа может давать разные результаты. Это может означать, что ваши файлы будут немного отличаться, или что программа вылетает, или программа форматирует ваш жесткий диск, или программа заставляет демонов вылетать из носа пользователей ( http://catb.org/jargon/html/N/nasal-demons.html)
Это не означает, что вам нужно определять значения массива при создании массива, но вы должны убедиться, что инициализируете любое значение массива, прежде чем использовать его. Конечно, самый простой способ убедиться в этом - сделать это при создании массива.
MyStruct array[10];
printf( "%f", array[2].v ); // POTENTIAL BANG!
array[3].v = 7.0;
...
printf( "%f", array[3].v ); // THIS IS OK.
Не забывайте, что для огромных массивов POD есть хороший способ инициализации всех членов до нуля
MyPODStruct bigArray[1000] = { 0 };
Я категорически не согласен с высказанным мнением о том, что "устранение общего источника ошибок" или "несоблюдение этого правила приведет к нарушению правильности вашей программы". Если программа работает с унифицированными значениями, то она имеет ошибку и неверна. Инициализация значений не устраняет эту ошибку, потому что они часто все еще не имеют ожидаемых значений при первом использовании. Однако, когда они содержат случайный мусор, программа с большей вероятностью аварийно завершает работу при каждой попытке. Постоянное использование одних и тех же значений может привести к более детерминированному поведению при сбое и облегчить отладку.
По вашему конкретному вопросу рекомендуется также перезаписывать неиспользуемые части перед их записью в файл, поскольку они могут содержать что-то из предыдущего использования, которое вы не хотите записывать, например пароли.
Можно было бы написать большую статью о разнице между двумя стилями, с которыми можно столкнуться: людьми, которые всегда инициализируют переменные при их объявлении, и людьми, которые инициализируют их при необходимости. Я делюсь большим проектом с кем-то, кто находится в первой категории, и теперь я определенно больше второго типа. Всегда инициализация переменных принесла больше тонких ошибок и проблем, чем нет, и я постараюсь объяснить почему, вспоминая случаи, которые я обнаружил. Первый пример:
struct NODE Pop(STACK * Stack)
{
struct NODE node = EMPTY_STACK;
if(Stack && Stack->stackPointer)
node = Stack->node[--Stack->stackPointer];
return node;
}
Это был код, написанный другим парнем. Эта функция самая горячая в нашем приложении (вы представляете текстовый индекс на 500 000 000 предложений в троичном дереве, стек FIFO используется для обработки рекурсии, поскольку мы не хотим использовать рекурсивные вызовы функций). Это было типично для его стиля программирования из-за его систематической инициализации переменных. Проблема с этим кодом была скрытой memcpy
инициализации и двух других копий структур (которые, кстати, не были вызовами memcpy
иногда это странно), поэтому у нас было 3 копии + скрытый вызов функции в самой горячей функции проекта. Переписать это
struct NODE Pop(STACK * Stack)
{
if(Stack && Stack->stackPointer)
return Stack->node[--Stack->stackPointer];
return EMPTY_STACK;
}
Только одна копия (и дополнительное преимущество в SPARC, где она выполняется, функция является конечной функцией благодаря избегаемому вызову memcpy
и не нужно строить новое окно регистрации). Так что функция была в 4 раза быстрее.
Еще одна проблема, которую я нашел унции, но не помню, где именно (так что нет примера кода, извините). Переменная, которая была инициализирована при объявлении, но использовалась в цикле, с switch
в конечном автомате. Проблема в том, что значение инициализации не было одним из состояний автомата, и в некоторых крайне редких случаях автомат работал некорректно. Удалив инициализатор, предупреждение, сгенерированное компилятором, сделало очевидным, что переменная может быть использована до ее правильной инициализации. Починить автомат тогда было легко.Мораль: защитная инициализация переменной может подавить очень полезное предупреждение компилятора.
Вывод: инициализируйте ваши переменные с умом. Делать это систематически - не что иное, как следовать культу груза (мой приятель на работе хуже, чем можно себе представить, он никогда не использует goto, всегда инициализирует переменную, использует много статических объявлений (это быстрее, вы знаете, это на самом деле даже очень медленно на SPARC 64bit), делает все функции inline
даже если у них есть 500 строк (используя __attribute__((always_inline))
когда компилятор не хочет)
Имейте в виду, что сохранение неинициализированных массивов может иметь такие преимущества, как производительность.
Это только плохое чтение из неинициализированных массивов. Иметь их без чтения неинициализированных мест - это хорошо.
Более того, если в вашей программе есть ошибка, из-за которой она читается из неинициализированного места в массиве, то "прикрытие" путем защитной инициализации всего массива известным значением не является решением для ошибки, и может только вывести ее на поверхность позже.
Если вы не инициализируете значения в массиве C++, тогда значения могут быть любыми, поэтому было бы неплохо обнулять их, если вы хотите получить предсказуемые результаты.
Но если вы используете массив символов в виде строки с нулевым символом в конце, вы сможете записать его в файл с соответствующей функцией.
Хотя в С ++ может быть лучше использовать более ООП-решение. IE векторы, строки и т. Д.
Во-первых, вы должны инициализировать массивы, переменные и т. Д., Если это не нарушит правильность вашей программы.
Во-вторых, похоже, что в данном конкретном случае не инициализация массива не повлияла на корректность исходной программы. Вместо этого, программа, предназначенная для сравнения файлов, недостаточно знает формат файла, используемый для определения значимых различий в файлах ("значимых", определенных первой программой).
Вместо того чтобы жаловаться на оригинальную программу, я бы исправил программу сравнения, чтобы узнать больше о рассматриваемом формате файла. Если формат файла недостаточно хорошо документирован, у вас есть веская причина для жалоб.
Я бы сказал, что хорошей практикой в C++ является использование std::vector<> вместо массива. Это не действительно для C, конечно.