2010-01-11 3 views
9

Недавно я столкнулся с ситуацией, когда мне нужно сравнить два файла (золотые и ожидаемые) для проверки результатов тестирования, и даже если данные, записанные для обоих файлов, были одинаковыми, файлы не совпадают.Хорошая практика для инициализации массива в C/C++?

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

Это вызвало у меня вопрос, правильно ли инициализировать массив в C/C++, как это делается на Java?

ответ

23

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

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

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

+7

всего несколько советов, которые идут в одном направлении: используйте минимальную область видимости для ваших переменных. Инициализируйте непосредственно после объявления. Используйте переменные только для одной цели. Используйте const, если вы намерены изменить свою переменную Значение. Основная причина этих подсказок: они улучшают читаемость и r создайте возможность для тонких ошибок при изменении кода. Теперь код может быть без ошибок, не следуя этим подсказкам, но после этого легче сохранить его при каждом изменении кода. –

+1

Определить и инициализировать в одном заявлении, когда это возможно. – 2010-01-11 14:47:07

+1

Нет, инициализируйте свою переменную там, где это уместно. Цикл for более читабельен, когда переменная цикла инициализируется в инструкции for. Ваша переменная могла быть объявлена ​​перед циклом. –

1

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

Но если вы используете массив символов как строку с завершающим нулем, то вы должны иметь возможность записать ее в файл с надлежащей функцией.

Хотя в C++ может быть лучше использовать большее решение ООП. И.Е. векторы, строки и т. д.

+0

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

5

Использование неопределенного значения в массиве приводит к неопределенному поведению. Таким образом, программа может производить разные результаты. Это может означать, что ваши файлы немного отличаются или программа сработает, или программа форматирует ваш жесткий диск, или программа заставляет демонов вылететь из носа пользователя (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. 

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

MyPODStruct bigArray[1000] = { 0 }; 
+0

+1 к сокращению, я использую этот стиль в достаточной сумме. Одно слово осторожности без конкретных доказательств этого, но я считаю, что формат = {0} можно оптимизировать так же, как Win32 ZeroMemory (http://msdn.microsoft.com/en-us/library/aa366920%28VS.85 % 29.aspx) API, с учетом пожеланий компиляторов и настроек оптимизации. – dirtybird

+0

Он может быть оптимизирован только в том случае, если компилятор может доказать, что он не сделает видимой разницы (т. Е. Все значения будут одинаково заданы в любом случае). В этом случае, когда массив записывается в файл, он определенно будет иметь заметную разницу. – caf

0

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

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

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

3

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

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

+0

+1. Я видел, что люди просто верят, что инициализация переменной решает все проблемы. Нет ... это просто помогает, когда вы инициализируете этот указатель на NULL, что всегда происходит kaboom * always *, вместо того, чтобы видеть, что проблема возникает здесь и там, и вытягивая ваши волосы, пытаясь отследить их. Существуют также сценарии, в которых вы отлаживаете и выполняете один и тот же блок в несколько раз, и если у вас есть логический недостаток, вы можете получить неожиданные пути, потому что переменная/массив/etc просто заканчивается в той же памяти местоположение, используя предыдущее значение. – dirtybird

0

Я бы сказал, что хорошая практика в C++ использует std :: vector <> вместо массива. Конечно, это не относится к C.

1

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

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

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

+0

+1 Это случается чаще, чем можно было бы подумать. –

1

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

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 gcc странно), поэтому у нас было 3 копии + скрытый вызов функции в самой горячей функции проекта , Переписав его

struct NODE Pop(STACK * Stack) 
{ 
    if(Stack && Stack->stackPointer) 
    return Stack->node[--Stack->stackPointer]; 
    return EMPTY_STACK; 
} 

только одну копию (и дополнительного пособия на SPARC, где он работает, функция является функцией листа благодаря избегали вызова к memcpy и не нужно строить новое окно регистров). Таким образом, функция была в 4 раза быстрее.

Еще одна проблема, которую я нашел унцию, но не помню, где именно (так что пример кода, извините). Переменная, которая была инициализирована при объявлении, но использовалась в цикле, с switch в автомате с конечным состоянием. Проблема в том, что значение инициализации не было одним из состояний автомата, и в некоторых экстремально редких случаях автомат работал неправильно. Удалив инициализатор, предупреждение, выпущенное компилятором, сделало очевидным, что переменная может использоваться до того, как она была правильно инициализирована. Фиксация автомата была легкой. Мораль: защитная инициализация переменной может подавить очень полезное предупреждение о компиляторе.

Заключение: Инициализируйте свои переменные с умом. Выполнение его систематично - это не что иное, как после грузового культа (мой приятель на работе - это худший грузчик, который можно себе представить, он никогда не использует goto, всегда инициализирует переменную, использует множество статических объявлений (это быстрее вы знаете (это на самом деле даже очень медленный на SPARC 64 бит), делает все функции inline, даже если они имеют 500 строк (с использованием __attribute__((always_inline)), когда компилятор не хочет)

+0

+1, спасибо вам здравомыслящий человек за этот ответ. –

Смежные вопросы