Проверка файла на ошибки c
В свете множества недавних статей, посвящённых статическому анализу кода на С++, пользователи неоднократно интересовались анализатором cppcheck. Это относительно молодой проект статического анализа с открытым исходным кодом, ориентированный в первую очередь на нахождение реальных ошибок в коде с минимальным количеством ложных срабатываний.
Совсем недавно cppcheck помог найти уязвимость в проекте Xorg, которая существовала там почти 23 года! Он помог уже тысячам программистов по всему миру, на официальном сайте можно найти информацию о найденных с помощью cppcheck уязвимостях в программах, и этот список постоянно растёт. Итак, если вы хотите знать, почему нужно использовать cppcheck всегда и везде — прошу под кат.
CppCat и cppcheck
Начну со сравнения этих утилит, поскольку данная просьба озвучивалась неоднократно в комментариях. Разработчики CppCat уже самостоятельно провели такое сравнение (с PVS-Studio), но с тех пор утекло много воды, а сравнение не очень объективно, так как PVS-Studio (насколько я понимаю всю замысловатость фразы «Please write us to get a price for PVS-Studio. Please specify interesting license type.») не предназначена для программистов-одиночек. CppCat же, как и cppcheck, доступен каждому (с оговоркой на привязку к VisualStudio определённых версий и лицензию на год).
Сравнить эти анализаторы будет непросто: у меня не имеется под руками ни одной версии Visual Studio под Linux. Поэтому сначала я ограничусь анализом уже проанализированного кода в недавнем обзоре CppCat: натравлю cppcheck на Notepad++, приведу статистику ошибок/предупреждений, которые можно будет сравнить с уже готовым анализом CppCat.
Параллельно пробую поставить в виртуальной машине CppCat. Надо сказать, что после того как Visual Studio 2010 был установлен, инсталлятор недолго думая выдал следующее:
Из-за чего тестирование усложнилось квестом найди-поставь-Visual-Studio-2013-переустанови-IE-11-перезагрузись-обновись, что на виртуалке, не обременённой обновлениями, заняло ровно полдня.
Что в итоге? При открытии проекта Notepad++ Visual Studio гордо завис. Попытка создать новый проект привела к тому, что в анализе CppCat посыпались ошибки о не найденных заголовочных файлах. По сему сравнивать придётся с тем, что имеем в предыдущей статье. Хотя Visual Studio я ставлю практически в первый раз, но эффект «юзабилити» налицо.
Подготовка
Так как cppcheck — проект с открытым исходным кодом, никто не мешает загрузить самую свежую версию из гита и скомпилировать её самостоятельно. Cppcheck предназначен для разработчиков, поэтому компиляция программы из исходного кода не должна вызывать каких-либо проблем:
Готово. Я специально взял свежую версию из гита и положил её в отдельную папку — с ней будет проще работать, так как в дальнейшем cppcheck можно сильно улучшить и настроить «под себя».
Тестовая конфигурация: RHEL 6.1, процессор i5-2400 @ 3.10GHz (для оценки времени работы анализатора).
Удобства ради действия будут производится в командной строке — при желании их можно будет повторить (в Linux:). Конечно, cppcheck имеет несколько плагинов для популярных IDE, но сегодня не об этом.
Анализ Notepad++
cppcheck устроен так, что все предупреждения отсортированы по категориям. По умолчанию включён только один вид анализа — error (ошибки). Ошибки нельзя игнорировать, так как если cppcheck выдал error — это место придётся переписывать в 99% случаев. Основной улов cppcheck — утечки памяти и переполнение буфера, а это уже чего-то стоит.
Может возникнуть резонный вопрос — как анализировать notepad++ в операционной системе Linux, если notepad++ использует исключительно WInAPI? Ответ прост — cppcheck на моей памяти единственный анализатор, который не привязан к сборочной среде или операционной системе. Он использует свой лексический анализатор, не требует обязательного присутствия всех заголовочных файлов, лояльно относится к хитросплетениям классов и т. п. Это замечательное свойство позволяет использовать cppcheck где угодно и для чего угодно, чего не скажешь о CppCat (см. квест по установке выше).
Анализ с помощью cppcheck прост до безобразия:
Пока проводится простейший анализ «из коробки». Команда определяет два параметра -q («тихий» режим — не выводить прогресс выполнения на экран) и -j4 — многопоточный анализ в 4 потока по количесву ядер процессора.
Результат выполнения предыдущей команды:
Время работы — 5 минут. В глаза сразу бросается ошибка "(error) Internal error. Token::Match called with varid 0. Please report this to Cppcheck developers". Это означает, что вместо бага в анализируемой программе нашёлся баг в самом анализаторе:) Сделаем скидку на то, что проект заточен под Win, и cppcheck не подозревает, что означают DWORD, LPTR и т. п. Возможно, в Win он покажет себя иначе.
Реально нашлась всего одна ошибка (с разницей в 2 строчки). Неплохо, возможно, что автор notepad++ сам пользуется cppcheck. Участок кода, который вызвал у cppcheck подозрение:
Это выглядит как ложное срабатывание, хотя исходники, честно говоря, шокируют. UPD: это всё-таки undefined behaviour, массив нужно освобождать с помощью оператора delete [].
Но это ещё не всё. Дело в том, что девизом cppcheck является отсутствие ложных срабатываний, то есть по умолчанию сканер ищет только очень критические ошибки — переполнение буфера, утечки памяти. После того как все ошибки найдены, можно просканировать повторно, включив флаги предупреждений:
Используется параметр --enable, который включает категории проверок:
— performance — проблемы производительности;
— portability — проблемы совместимости;
— warning — предупреждения — подозрительные места программы;
— style — ошибки стиля программирования.
В таком режиме вылавливается львиная доля стилистических/логических ошибок и потенциальных багов (т. е. ошибки, в которых cppcheck «не уверен»). Время сканирования — 5 минут. Результат я сразу отправил в файл, чтобы собрать статистику.
Небольшая статистика по типу найденных ошибок:
Море переменных не инициализировано в конструкторе: (warning) Member variable % is not initialized in the constructor. Это ошибку cppcheck считает предупреждением. Возможно, поведение такого кода зависит от компилятора, потому что npp каким-то чудом работает.
Инициализация переменной не в конструкторе, а в функции init, так что на мой взгляд ворнинги по делу.
(style) Same expression on both sides of '||'. Проверка одного и того же условия. Эту же ошибку выдавал CppCat, однако то ли в той статье версия npp старая, то ли ошибку уже пофиксили, но сейчас тот самый код выглядит так:
А это — новая добыча cppcheck:
(performance) Variable 'lineIndent' is reassigned a value before the old one has been used. По сути — двойное присваивание. Обычно это следствие копипасты, но cppckeck характеризует такую ошибку как ошибку производительности. Этот код стоит проверить, так как неизвестно, что подразумевал автор программы. Таких двойных присваиваний, а также неиспользуемых значений переменных по коду очень много:
Это предупреждение обычно бесполезно — редко, когда в данном участке кода есть ошибка, просто при рефакторинге забыли удалить старое значение.
(portability) The extra qualification 'FunctionListPanel::' is unnecessary and is considered an error by many compilers. Полезное предупреждение, которого физически нет и не планируется в CppCat: ошибки переносимости между разными платформами (portability). Данный кусок кода будет работать не во всех компиляторах:
(style) Exception should be caught by reference. Интересное предупреждение — ловить исключение следует по ссылке, а не по значению:
(style) Consecutive return, break, continue, goto or throw statements are unnecessary. Мёртвый код: break после return:
(warning) Assignment of function parameter has no effect outside the function. Обычно это полезное предупреждение, сигнализирующее об опечатке — значение, присвоенное внутри функции, никуда не передаётся. Однако здесь это очевидно ложное срабатывание, так как value — переменная класса:
(portability) scanf without field width limits can crash with huge input data on some versions of libc. Подобная привычка использования scanf может привести к опасному переполнению буфера. В случае числовых переменных это банальный undefined behaviour:
Для преобразования чисел лучше использовать безопасный strtol.
Ещё один представитель:
Здесь нет переполнения буфера только потому, что wordBuffer и sKeywordBuffer одинакового размера.
(style) 'TiXmlStringA::operator=' should return 'TiXmlStringA &'. Оператор = возвращает void:
С таким оператором нельзя использовать стандартную для C++ цепочку:
(warning) The class 'ControlsTab' defines member variable with name '_isVertical' also defined in its parent class 'TabBar'. Ошибка двойного определения переменной в классе:
которая уже определена в родительском классе:
Не являясь экспертом в плюсах, не могу сразу ответить, можно ли так делать (protected/private).
(style) Found duplicate branches for 'if' and 'else'. Аналогичные ошибки нашёл и CppCat. Лишнее условие:
Думаю, если бы у меня были заголовочные файлы винды, можно указать путь к ним через параметр -I, тогда ошибок будет значительно больше.
(style) Array index 'j' is used before limits check. Несмотря на то что предупреждение низкоприоритетное, найденная ошибка представляет опасность выхода за границы массива:
Учитывая, что параметр startcol внешний, можно вылететь за границу массива, не говоря об индексе -1.
(style) Unsigned variable 'i' can't be negative so it is unnecessary to test it. Условие в цикле всегда положительно => бесконечный цикл:
Этой ошибки можно было бы избежать при сборке проекта компилятором gcc и флагами -Wall -Wextra. Думаю, такая ошибка часто появляется при рефакторинге проекта по другой ошибке компилятора — несоответствие типов. Было int — стало unsigned, вот и результат.
Мелкие недочёты
(style) Unused variable: ent. Это предупреждение умеют выдавать и компиляторы, ничего интересного.
(warning) %d in format string (no. 2) requires 'int' but the argument type is 'DWORD ' — очень популярная ошибка, программисты в printf пишут тип, не соответствующий типу переменной. Это тоже отстреливается большинством компиляторов.
Класс без конструктора:
(performance) Function parameter 'range' should be passed by reference. cppcheck рекомендует передавать параметр по ссылке, чтобы избежать копирования аргумента:
(warning) Ineffective call of function 'empty()'. Did you intend to call 'clear()' instead? Метод empty имеет смысл только внутри условия и не очищает строку. Это ложное срабатывание, cppcheck не подозревал, что автор сделает свой класс String:) Следует проверить логику именования методов.
(style) 'class ByteArray' does not have a copy constructor which is recommended since the class contains a pointer to allocated memory. cppcheck просто рекомендует создать в классе отсутствующий конструктор копирования на случай, если программист забыл его реализовать.
Вывод
Оба анализатора наковыряли приличное количество ошибок, причём многие из них уникальны для каждого анализатора. В целом, было бы неплохо иметь cppcheck под рукой всегда, так как он открыт, кроссплатформенный, реально находит ошибки и помогает улучшать стиль программирования. Использование cppcheck обычно не представляет проблем.
Из этого анализа можно сделать вывод, что инструменты хорошо друг друга дополняют. Для кого-то главным минусом cppcheck является отсутствие плагина под Visual Studio, поэтому авторы cppcheck любезно предлагают попробовать PVS-Studio. Несмотря на интерфейс командной строки, пользоваться cppcheck очень удобно. Не требуется ни компилятора, ни IDE, ни заголовочных файлов — это самый простой в использовании статический анализатор, который я только видел. Кроме того, я специально поставил в виртуальной машине сборку cppcheck для Windows — она имеет приятный графический интерфейс, устанавливается быстро и без проблем выполняет анализ:
Результат анализа можно экспортировать в XML и смотреть в браузере.
Данный анализ можно было бы сильно улучшить, если явно указать cppcheck, где искать заголовочные файлы, какие функции выделяют и освобождают память. Так как статья получилась большой, про то, как настроить cppcheck под конкретный проект, улучшить качество анализа и писать свои правила для cppcheck — в следующий раз.
P. S. Прошу простить одну глупость. Cppcheck имеет в настройках возможность анализировать код специально для Windows, из-за чего очень много интересных ошибок было пропущено. Нужно было анализировать npp с флагом --platform=win32A.
Качество приложения можно улучшить путем регулярного выполнения анализа кода C или C++. Анализ кода помогает найти распространенные проблемы и нарушения хорошей практики программирования. И находит дефекты, которые трудно обнаружить с помощью тестирования. Его предупреждения отличаются от ошибок и предупреждений компилятора: он выполняет поиск конкретных шаблонов кода, которые вызывают проблемы. Это означает, что код является допустимым, но может по-прежнему создавать проблемы для вас или для других пользователей, использующих ваш код.
Анализ кода
Средства анализа выполняют поиск общих проблем в коде, которые могут привести к ошибкам времени выполнения или проблемам управления кодом.
Анализ кода C++
Чтобы выполнить анализ кода C++, запустите статический анализ кода. Запустить этот компонент после устранения всех очевидных ошибок, препятствующих успешной сборке, и потратить некоторое время, чтобы устранить создаваемые им предупреждения, — очень полезная привычка. Вы сможете избавиться от определенных будущих проблем, а также научитесь некоторым полезным приемам написания кода.
Нажмите клавиши ALT+F11 (или выберите в верхнем меню команду Анализ > Выполнить анализ кода в решении) для запуска статического анализа кода.
Все новые или обновленные предупреждения отображаются на вкладке Список ошибок в нижней части интегрированной среды разработки. Щелкните предупреждение для перехода к нему в коде.
Запуск очистки кода
Помимо форматирования пробелов, отступов и т. п., функция Очистка кода применяет определенные вами соглашения о стиле кода. Ваши настройки для каждого стиля кода считываются из файла EditorConfig, если такой существует в проекте, или из раздела Параметры стиля кода, который доступен через диалоговое окно Параметры.
Запуск модульных тестов
Модульные тесты — это первая линия защиты от ошибок в коде, так как при правильном проведении они позволяют проверять отдельные "модули" кода (как правило, это отдельные функции), которые проще отлаживать, чем всю программу. Visual Studio устанавливает платформу модульного тестирования Майкрософт для управляемого и машинного кода. Платформа модульного тестирования используется для создания модульных тестов, их запуска и передачи результатов таких тестов. Завершив внесение изменений, запустите модульные тесты повторно, чтобы убедиться, что код по-прежнему работает правильно. При использовании выпуска Visual Studio Enterprise можно настроить автоматический запуск тестов после каждой сборки.
Чтобы приступить к работе с модульными тестами, ознакомьтесь со статьей Создание модульных тестов для кода с помощью IntelliTest.
Дополнительные сведения о модульных тестах в Visual Studio, а также о том, как они могут помочь в создании более качественного кода, см. в статье Основные сведения о модульных тестах.
Функции setjmp() и longjmp()
Вот мы и подошли к самому интересному – функциям нелокальных переходов. setjmp() и longjmp() работают по принципу goto, но в отличие от него позволяют перепрыгивать из одного места в другое в пределах всей программы, а не одной функции.
int setjmp(jmp_buf env);
Сохраняет информацию о контексте выполнения программы (регистры микропроцессора и прочее) в env . Возвращает 0 , если была вызвана напрямую или value , если из longjmp() .
void longjmp(jmp_buf env, int value);
Восстанавливает контекст выполнения программы из env , возвращает управление setjmp() и передаёт ей value .
Используя setjmp() и longjmp () можно реализовать механизм исключений. Во многих языках высокого уровня (например, в Perl) исключения реализованы через них.
Внимание! Функции setjmp() и longjmp () в первую очередь применяются в системном программировании, и их использование в клиентском коде не рекомендуется. Их применение ухудшает читаемость программы и может привести к непредсказуемым ошибкам. Например, что произойдёт, если вы прыгните не вверх по стеку – в вызывающую функцию, а в параллельную, уже завершившую выполнение?
Задание простых точек останова
Точки останова — это один из самых простых и важных компонентов надежной отладки. Точка останова указывает, где Visual Studio следует приостановить выполнение кода, чтобы вы могли проверить значения переменных или поведение памяти либо выполнение ветви кода. После установки или удаления точек останова перестраивать проект не нужно.
Установите точку останова, щелкнув дальнее поле строки, в которой требуется приостановить выполнение, или нажмите клавишу F9, чтобы установить точку останова в текущей строке кода. Выполнение кода прерывается (останавливается) перед инструкциями для этой строки кода.
Чаще всего точки останова используются для решения следующих задач.
Чтобы точнее определить источник аварийного завершения или отсутствия отклика программы, расставьте точки останова вокруг и непосредственно в коде вызова метода, который, по вашему мнению, приводит к сбою. При выполнении кода в отладчике удаляйте, а затем снова устанавливайте точки останова ближе друг к другу, пока не найдете строку кода, вызывающую ошибку. Выполнение кода в отладчике описывается в следующем разделе.
При добавлении нового кода установите точку останова в его начале и выполните код, чтобы убедиться в том, что он работает правильно.
При реализации сложного поведения задайте точки останова для алгоритмического кода, чтобы можно было проверить значения переменных и данные при прерывании программы.
При написании кода C или C++ используйте точки останова для остановки кода, чтобы можно было проверить значения адреса (ищите значение NULL) и просмотреть значения счетчиков при отладке ошибок, связанных с памятью.
Дополнительные сведения о точках останова см. в статье Использование точек останова.
Использование быстрых действий для исправления или рефакторинга кода
Если вы привыкли работать с клавиатурой, вы можете использовать клавиши со стрелками и сочетание клавиш CTRL+ . для проверки возможностей оптимизации и очистки кода!
Создание рабочих элементов для предупреждений анализа кода
Для регистрации ошибок в журнале из среды Visual Studio можно использовать функцию отслеживания рабочих элементов. чтобы использовать эту функцию, необходимо подключиться к экземпляру Azure DevOps Server (ранее, Team Foundation Server).
Сборка кода
Существует два основных типа конфигурации сборки: отладка и выпуск. При использовании конфигурации отладка создается более крупный и медленный исполняемый файл, обеспечивающий более широкие интерактивные возможности отладки во время выполнения. Исполняемый файл конфигурации отладка никогда не следует отправлять. Конфигурация выпуск позволяет создать более быстрый оптимизированный исполняемый файл, подходящий для отправки (по крайней мере с точки зрения компилятора). По умолчанию используется конфигурация Отладка.
Самый простой способ выполнить сборку проекта — нажать клавишу F7, однако вы также можете начать сборку, выбрав в главном меню пункты Сборка > Собрать решение.
Процесс сборки можно наблюдать в окне Вывод в нижней части пользовательского интерфейса Visual Studio. Здесь отображаются ошибки, предупреждения и операции сборки. При наличии ошибок (или предупреждений выше заданного уровня) сборка завершится ошибкой. Можно щелкнуть ошибку и предупреждение, чтобы перейти к строке, где они возникли. Для перестроения проекта можно нажать клавишу F7 (чтобы перекомпилировать только файлы с ошибками) или CTRL+ALT+F7 (для чистого полного перестроения).
После успешного выполнения построения вы увидите примерно следующие результаты в окне Вывод:
Функции работы с errno
Получив код ошибки, хочется сразу получить по нему её описание. К счастью, ISO C предлагает целый набор полезных функций.
void perror(const char *s);
strerror() не безопасная функция. Во-первых, возвращаемая ею строка не является константной. При этом она может храниться в статической или в динамической памяти в зависимости от реализации. В первом случае её изменение приведёт к ошибке времени выполнения. Во-вторых, если вы решите сохранить указатель на строку, и после вызовите функцию с новым кодом, все прежние указатели будут указывать уже на новую строку, ибо она использует один буфер для всех строк. В-третьих, её поведение в многопоточной среде не определено в стандарте. Впрочем, в QNX она объявлена как thread safe.
Поэтому в новом стандарте ISO C11 были предложены две очень полезные функции.
size_t strerrorlen_s(errno_t errnum);
Возвращает длину строки с описанием ошибки errnum .
errno_t strerror_s(char *buf, rsize_t buflen, errno_t errnum);
Копирует строку с описание ошибки errnum в буфер buf длиной buflen .
Функции входят в Annex K (Bounds-checking interfaces), вызвавший много споров. Он не обязателен к выполнению и целиком не реализован ни в одной из свободных библиотек. Open Watcom C/C++ (Windows), Slibc (GNU libc) и Safe C Library (POSIX), в последней, к сожалению, именно эти две функции не реализованы. Тем не менее, их можно найти в коммерческих средах разработки и системах реального времени, Embarcadero RAD Studio, INtime RTOS, QNX.
Стандарт POSIX.1-2008 определяет следующие функции:
char *strerror_l(int errnum, locale_t locale);
Возвращает строку, содержащую локализованное описание ошибки errnum , используя locale . Безопасна в многопоточной среде. Не реализована в Mac OS X, FreeBSD, NetBSD, OpenBSD, Solaris и прочих коммерческих UNIX. Реализована в Linux, MINIX 3 и Illumos (OpenSolaris).
int strerror_r(int errnum, char *buf, size_t buflen);
Копирует строку с описание ошибки errnum в буфер buf длиной buflen . Если buflen меньше длины строки, лишнее обрезается. Безопасна в многоготочной среде. Реализована во всех UNIX.
Увы, никакого аналога strerrorlen_s() в POSIX не определили, поэтому длину строки можно выяснить лишь экспериментальным путём. Обычно 300 символов хватает за глаза. GNU C Library в реализации strerror() использует буфер длиной в 1024 символа. Но мало ли, а вдруг?
Стандартные наборы правил C/C++
Visual Studio включает следующие стандартные наборы правил для машинного кода:
Набор правил | Описание |
---|---|
C++ Core Check арифметические правила | Эти правила применяют проверки, связанные с арифметическими операциями, из C++ Core Guidelines. |
Правила C++ Core Check границ | Эти правила применяют профиль границ C++ Core Guidelines. |
Правила класса C++ Core Check | Эти правила применяют проверки, связанные с классами, из C++ Core Guidelines. |
Правила параллелизма C++ Core Check | Эти правила применяют проверки, связанные с параллелизмом, из C++ Core Guidelines. |
Правила констант C++ Core Check | Эти правила применяют к C++ Core Guidelines проверки, связанные с константами. |
Правила объявления C++ Core Check | Эти правила применяют проверки, связанные с объявлениями, из C++ Core Guidelines. |
Правила перечисления C++ Core Check | Эти правила применяют проверки, связанные с перечислением, из C++ Core Guidelines. |
C++ Core Check экспериментальных правил | Эти правила собираются некоторые экспериментальные проверки. В итоге мы планируем, чтобы эти проверки были перемещены на другие наборы правил или полностью удалены. |
C++ Core Check правила функции | Эти правила применяют проверки, связанные с функциями, из C++ Core Guidelines. |
C++ Core Check правила GSL | Эти правила применяют проверки, связанные с библиотекой поддержки руководств, из C++ Core Guidelines. |
Правила времени жизни C++ Core Check | Эти правила применяют профиль времени существования C++ Core Guidelines. |
Правила указателя владельца C++ Core Check | Эти правила применяют проверки управления ресурсами, связанные owner с C++ Core Guidelines. |
Правила необработанных указателей C++ Core Check | Эти правила применяют проверки управления ресурсами, связанные с необработанными указателями, из C++ Core Guidelines. |
Правила C++ Core Check | Эти правила применяют подмножество проверок из C++ Core Guidelines. Используйте этот набор правил, чтобы включить все правила C++ Core Check, за исключением перечисления и экспериментальных наборов правил. |
C++ Core Check правила общих указателей | Эти правила применяют проверки управления ресурсами, связанные с типами с семантикой общего указателя из C++ Core Guidelines. |
Правила STL C++ Core Check | Эти правила применяют проверки, связанные с библиотекой стандартных шаблонов C++ (STL), из C++ Core Guidelines. |
Правила стилей C++ Core Check | Эти правила применяют проверки, связанные с использованием выражений и инструкций, из C++ Core Guidelines. |
Правила типа C++ Core Check | Эти правила применяют профиль типа C++ Core Guidelines. |
Правила для уникальных указателей C++ Core Check | Эти правила применяют проверки управления ресурсами, связанные с типами с семантикой уникальных указателей, из C++ Core Guidelines. |
Правила проверки параллелизма | Эти правила применяют набор проверок шаблона параллелизма Win32 в C++. |
Правила параллелизма | Добавляет правила параллелизма из C++ Core Guidelines правил проверки на параллелизм. |
Минимальные правила Майкрософт для машинного кода | Эти правила касаются наиболее важных проблем в собственном коде, включая потенциальные бреши в системе безопасности и сбои приложений. Мы рекомендуем включить этот набор правил в любой настраиваемый набор правил, создаваемый для собственных проектов. |
Рекомендуемые Майкрософт правила для машинного кода | Эти правила касаются наиболее важных и распространенных проблем в собственном коде. Эти проблемы включают в себя потенциальные бреши в системе безопасности и сбои приложений. Мы рекомендуем включить этот набор правил в любой настраиваемый набор правил, создаваемый для собственных проектов. этот набор правил предназначен для работы с Visual Studio Professional edition и более поздних версий. Он включает все правила в Microsoft Native с минимальными правилами. |
Visual Studio включает следующие стандартные наборы правил для управляемого кода:
Набор правил | Описание |
---|---|
Основные правила корректности Microsoft | Эти правила касаются логических ошибок и распространенных ошибок, возникающих при использовании API-интерфейсов платформы. Включите этот набор правил, чтобы развернуть список предупреждений, о которых сообщили минимальные Рекомендуемые правила. |
Базовые правила разработки Microsoft Basic | Эти правила посвящены применению рекомендаций для упрощения понимания и использования кода. Включите этот набор правил, если проект включает в себя код библиотеки или если вы хотите применить рекомендации по легкому обслуживанию кода. |
Расширенные правила корректности Майкрософт | Эти правила расширяют основные правила корректности, позволяющие максимально увеличить обнаруженную логику и ошибки использования платформы. Особое внимание уделяется конкретным сценариям, например COM-взаимодействию и мобильным приложениям. Рассмотрите возможность включения этого набора правил, если один из этих сценариев применяется к проекту или для поиска дополнительных проблем в проекте. |
Расширенные правила рекомендации по проектированию Майкрософт | Эти правила расширяют базовые правила разработки, позволяющие получить максимальную выгоду от проблем использования и удобства обслуживания. Особое внимание уделяется рекомендациям по именованию. Рассмотрите возможность включения этого набора правил, если проект включает в себя библиотечный код или если вы хотите применять самые высокие стандарты для написания кода, поддерживающего обслуживание. |
Правила глобализации корпорации Майкрософт | Эти правила касаются проблем, которые предотвращают правильное отображение данных в приложении при использовании на разных языках, языковых стандартах и культурах. Включите этот набор правил, если приложение локализовано и (или) глобализовано. |
Минимальное число управляемых корпорацией Майкрософт правил | эти правила касаются наиболее важных проблем в коде, для которых Code Analysis является наиболее точным. эти правила имеют небольшой номер, и они предназначены только для работы в ограниченных Visual Studio выпусках. используйте минимумрекоммендедрулес. ruleset с другими выпусками Visual Studio. |
Рекомендуемые правила, управляемые Майкрософт | Эти правила касаются наиболее важных проблем в коде. Эти проблемы включают в себя потенциальные бреши в системе безопасности, сбои приложений, а другая важная логика и ошибки проектирования. Мы рекомендуем включить этот набор правил в любой настраиваемый набор правил, создаваемый для проектов. |
Минимальные правила Microsoft Mixed (C++/CLR) | Эти правила касаются наиболее важных проблем в проектах C++, поддерживающих среду CLR. Эти проблемы включают в себя потенциальные бреши в системе безопасности, сбои приложений, а другая важная логика и ошибки проектирования. Мы рекомендуем включить этот набор правил в любой настраиваемый набор правил, создаваемый для проектов C++, поддерживающих среду CLR. |
Рекомендуемые правила Microsoft Mixed (C++/CLR) | Эти правила касаются наиболее распространенных и критических проблем в проектах C++, поддерживающих среду CLR. Эти проблемы включают в себя потенциальные бреши в системе безопасности, сбои приложений, а другая важная логика и ошибки проектирования. этот набор правил предназначен для использования в Visual Studio Professional edition и более поздних версиях. |
Правила безопасности Майкрософт | Этот набор правил содержит все правила безопасности Майкрософт. Включите этот набор правил, чтобы максимально увеличить количество потенциальных проблем безопасности, о которых сообщается. |
Чтобы включить каждое правило, выполните следующие действия.
Просмотр списка ошибок
Если вы внесли какие-либо изменения в код, который был ранее и успешно скомпилирован, возможно, возникнет ошибка. Если вы новичок в написании кода, возможно, их будет много. Ошибки иногда очевидны, например простая синтаксическая ошибка или неправильное имя переменной, а иногда их причину трудно выяснить, имея в распоряжении только зашифрованный код. Чтобы получить более четкое представление о проблеме, перейдите вниз окна Вывод сборки и щелкните вкладку Список ошибок. При этом вы перейдете к более организованному представлению ошибок и предупреждений для проекта и получите доступ к некоторым дополнительным параметрам.
Щелкните строку ошибки в окне Список ошибок, чтобы перейти в строку кода, в которой возникла ошибка. (Кроме того, номера строк можно включить, нажав клавиши Ctrl+Q, введя номера строк, а затем выбрав Включить или отключить отображение номеров строк в результатах. Это самый быстрый способ перехода в диалоговое окно Параметры, где можно включить номера строк.
Нажмите клавиши CTRL+G для быстрого перехода к номеру строки, в которой возникла ошибка.
Ошибку можно узнать по подчеркиванию красной волнистой линией Чтобы получить дополнительные сведения, наведите на нее указатель мыши. Внесите исправления, и подчеркивание исчезнет, хотя в результате исправления может возникнуть новая ошибка (это называется "регрессия").
Пройдите список ошибок и устраните все ошибки в коде.
Переменная errno и коды ошибок
errno – переменная, хранящая целочисленный код последней ошибки. В каждом потоке существует своя локальная версия errno, чем и обусловливается её безопасность в многопоточной среде. Обычно errno реализуется в виде макроса, разворачивающегося в вызов функции, возвращающей указатель на целочисленный буфер. При запуске программы значение errno равно нулю.
Стандарт ISO C определяет следующие коды:
- EDOM – (Error domain) ошибка области определения.
- EILSEQ – (Error invalid sequence) ошибочная последовательность байтов.
- ERANGE – (Error range) результат слишком велик.
Нехитрый скрипт печатает в консоль коды ошибок, их символические имена и описания:
Если вызов функции завершился ошибкой, то она устанавливает переменную errno в ненулевое значение. Если же вызов прошёл успешно, функция обычно не проверяет и не меняет переменную errno. Поэтому перед вызовом функции её нужно установить в 0 .
Как видите, описания ошибок в спецификации функции iconv() более информативны, чем в .
Просмотр подробных сведений об ошибках
Многие ошибки трудны для восприятия, будучи представленными в терминах компилятора. В этом случае могут потребоваться дополнительные сведения. Из окна Список ошибок можно выполнить автоматический поиск в поисковой системе Bing для получения дополнительных сведений об ошибке или предупреждении. Щелкните правой кнопкой мыши по соответствующей строке записи и выберите Показать справочные сведения об ошибке из контекстного меню или щелкните гиперссылку с кодом ошибки в столбце код в списке ошибок.
В зависимости от настроек результаты поиска по коду и описанию ошибки откроются в веб-браузере либо во вкладке Visual Studio с результатами поиска Bing. Представленные результаты — из различных источников в Интернете, и, возможно, не все они будут полезными.
Обработка исключений при операциях ввода-вывода
По причине зависимости от операционной системы иногда идентичные условия (например, отсутствие указанного каталога) могут создавать в методах ввода-вывода любое исключение из класса ввода-вывода. Это означает, что при вызове интерфейсов API ввода-вывода ваш код должн быть готов обработать все такие исключения или большую их часть, как показано в следующей таблице:
Сопоставление кодов ошибок с исключениями
Например, при вызове метода в операционной системе Windows код ошибки ERROR_FILE_NOT_FOUND (или 0x02) преобразуется в исключение FileNotFoundException, а код ошибки ERROR_PATH_NOT_FOUND (или 0x03) — в DirectoryNotFoundException.
К сожалению, точные условия возникновения определенных кодов ошибок в операционной системе часто не документируются или документируются в недостаточном объеме. Это означает, что возможны непредвиденные исключения. Например, при работе с каталогом логично ожидать, что передача недопустимого пути в конструктор DirectoryInfo приведет к созданию исключения DirectoryNotFoundException. Но в этой ситуации может создаваться и FileNotFoundException.
Функции atexit(), exit() и abort()
int atexit(void (*func)(void));
Регистрирует функции, вызываемые при нормальном завершении работы программы в порядке, обратном их регистрации. Можно зарегистрировать до 32 функций.
_Noreturn void exit(int exit_code);
Главное преимущество exit() в том, что она позволяет завершить программу не только из main() , но и из любой вложенной функции. К примеру, если в глубоко вложенной функции выполнилось (или не выполнилось) некоторое условие, после чего дальнейшее выполнение программы теряет всякий смысл. Подобный приём (early exit) широко используется при написании демонов, системных утилит и парсеров. В интерактивных программах с бесконечным главным циклом exit() можно использовать для выхода из программы при выборе нужного пункта меню.
_Noreturn void abort(void);
Вызывает аварийное завершение программы, если сигнал не был перехвачен обработчиком сигналов. Временные файлы не уничтожаются, закрытие потоков определяется реализацией. Самое главное отличие вызовов abort() и exit(EXIT_FAILURE) в том, что первый посылает программе сигнал SIGABRT , его можно перехватить и произвести нужные действия перед завершением программы. Записывается дамп памяти программы (core dump file), если они разрешены. При запуске в отладчике он перехватывает сигнал SIGABRT и останавливает выполнение программы, что очень удобно в отладке.
Вывод в отладчике:
В случае критической ошибки нужно использовать функцию abort() . К примеру, если при выделении памяти или записи файла произошла ошибка. Любые дальнейшие действия могут усугубить ситуацию. Если завершить выполнение обычным способом, при котором производится сброс потоков ввода — вывода, можно потерять ещё неповрежденные данные и временные файлы, поэтому самым лучшим решением будет записать дамп и мгновенно завершить программу.
В случае же некритической ошибки, например, вы не смогли открыть файл, можно безопасно выйти через exit() .
Отладка выполняемого кода
Успешно завершив сборку кода и его очистку, запустите код, нажав клавишу F5 или выбрав команду Отладка > Начать отладку. Приложение будет запущено в среде отладки, и вы сможете пронаблюдать его поведение. Интегрированная среда разработки Visual Studio изменяется во время выполнения приложения: окно Вывод заменяется двумя новыми окнами (в конфигурации окон по умолчанию): окном с вкладками Видимые/Локальные/Контрольные значения и окном с вкладками Стек вызовов/Точки останова/Параметры исключений/Вывод. Эти окна имеют несколько вкладок, которые позволяют просмотреть и проверить переменные, потоки, стеки вызовов приложения и другие характеристики поведения во время выполнения приложения.
Остановите приложение, нажав клавиши SHIFT+F5 или кнопку Остановить. Кроме того, можно просто закрыть главное окно приложения (или диалоговое окно командной строки).
Обработка IOException
IOException является базовым классом для исключений в пространстве имен System.IO и создается для любого кода ошибки, который не имеет сопоставления с определенным типом исключения. Это означает, что оно может появиться в любой операции ввода-вывода.
Так как IOException является базовым классом для других типов исключений в пространстве имен System.IO, его нужно обрабатывать в блоке catch после обработки других исключений, связанных с вводом-выводом.
Обратите внимание, что в коде обработки исключений IOException всегда нужно обрабатывать последним. Иначе блоки catch для производных классов не проверяются, ведь это исключение является базовым классом для всех остальных.
В случае IOException можно получить дополнительные сведения об ошибке из свойства IOException . Чтобы преобразовать значение HResult в код ошибки Win32, отбросьте верхние 16 бит из 32-разрядного значения. В приведенной ниже таблице перечислены коды ошибок, которые могут быть заключены в IOException.
HResult | Константа | Описание |
---|---|---|
ERROR_SHARING_VIOLATION | 32 | Отсутствует имя файла, или файл или каталог уже используется. |
ERROR_FILE_EXISTS | 80 | Файл уже существует. |
ERROR_INVALID_PARAMETER | 87 | Методу передан недопустимый аргумент. |
ERROR_ALREADY_EXISTS | 183 | Файл или каталог уже существует. |
Для обработки этих исключений можно применить предложение When в инструкции catch, как показано в приведенном ниже примере.
Настройка наборов правил для проекта
в Обозреватель решенийоткройте контекстное меню для имени проекта и выберите пункт свойства.
При необходимости в списках Конфигурация и платформа выберите конфигурацию сборки и целевую платформу.
для выполнения анализа кода при каждом построении проекта с использованием выбранной конфигурации установите флажок включить Code Analysis при сборке . вы также можете запустить анализ кода вручную, открыв меню анализ , а затем выбрав run Code Analysis onимя_проекта или run Code Analysis on File.
Выберите набор правил , который вы хотите использовать, или создайте Настраиваемый набор правил. если используется LLVM/clang-cl, см. раздел использование Clang-Tidy в Visual Studio для настройки параметров анализа Clang-Tidy.
Макрос assert()
Макрос, проверяющий условие expression (его результат должен быть числом) во время выполнения. Если условие не выполняется ( expression равно нулю), он печатает в stderr значения __FILE__ , __LINE__ , __func__ и expression в виде строки, после чего вызывает функцию abort() .
Если макрос NDEBUG определён перед включением , то assert() разворачивается в ((void) 0) и не делает ничего. Используется в отладочных целях.
Поиск и фильтрация результатов анализа кода
Можно выполнять поиск в длинных списках предупреждений, а также фильтровать предупреждения в решениях, состоящих из нескольких проектов.
Чтобы отфильтровать предупреждения по названию или идентификатору предупреждения: введите ключевое слово в поле поиска список ошибок.
Visual Studio включает эффективный интегрированный набор средств сборки и отладки проектов. Из этой статьи вы узнаете, как Visual Studio может помочь обнаружить проблемы в коде с помощью построения выходных данных, анализа кода, средств отладки и модульных тестов.
Мы разобрались, как работать с редактором, и написали код. Теперь необходимо убедиться, что код работает должным образом. Отладка в Visual Studio, как и в большинстве интегрированных сред разработки (IDE), осуществляется в два этапа: построение кода для обнаружения и устранения ошибок проекта и компилятора и выполнение кода для обнаружения ошибок времени выполнения и динамических ошибок.
Анализ и устранение предупреждений функции анализа кода
В окне список ошибок перечислены найденные предупреждения анализа кода. Результаты отображаются в таблице. Если есть дополнительные сведения о конкретном предупреждении, первый столбец содержит элемент управления расширения. Выберите его, чтобы развернуть на экране Дополнительные сведения о данной ошибке. По возможности функция анализа кода выводит на экран номера строк и логику анализа, вызвавшие предупреждение.
Для получения подробных сведений о предупреждении, включая возможные решения проблемы, выберите идентификатор предупреждения в столбце код, чтобы отобразить соответствующую статью справки в Интернете.
дважды щелкните предупреждение, чтобы переместить курсор в строку кода, вызвавшую предупреждение, в редакторе кода Visual Studio. Или нажмите клавишу ВВОД для выбранного предупреждения.
Поняв, в чем заключается проблема, можно разрешить ее в коде. Затем повторно запустите анализ кода, чтобы убедиться, что предупреждение больше не отображается в список ошибок.
Запуск анализа кода
на странице Code Analysis диалогового окна свойства Project можно настроить выполнение анализа кода каждый раз при сборке проекта. Кроме того, можно запустить анализ кода вручную.
Чтобы запустить анализ кода для решения, выполните следующие действия.
- в меню сборка выберите пункт запустить Code Analysis в решении.
Чтобы запустить анализ кода для проекта, выполните следующие действия.
в Обозреватель решений выберите имя проекта.
в меню сборка выберите команду выполнить Code Analysis дляProject имя.
Выполнение анализа кода для файла:
в Обозреватель решений выберите имя файла.
в меню сборка выберите команду выполнить Code Analysis в файле или нажмите клавиши Ctrl + Shift + Alt + F7.
Будет выполнена компиляция проекта или решения и запуск анализа кода. Результаты отображаются в окне список ошибок.
Создание рабочего элемента для одного или нескольких предупреждений кода C/C++
В список ошибок разверните и выберите предупреждения.
В контекстном меню предупреждений выберите команду создать рабочий элемент, а затем выберите тип рабочего элемента.
Visual Studio создает один рабочий элемент для выбранных предупреждений и отображает рабочий элемент в окне документа интегрированной среды разработки.
Добавьте дополнительные сведения, а затем выберите сохранить рабочий элемент.
Проверка кода во время выполнения
Когда выполнение кода приостанавливается из-за достижения точки останова, строка кода, помеченная желтым цветом (текущий оператор), еще не выполнена. Вы можете выполнить текущий оператор и проверить, как изменились значения. Для выполнения кода в отладчике можно использовать ряд команд пошагового выполнения. Если отмеченный код является вызовом метода, вы можете выполнить шаг с заходом, нажав клавишу F11. Кроме того, можно выполнить шаг с обходом строки кода, нажав клавишу F10. Дополнительные команды и подробные сведения о пошаговом выполнении кода см. в статье Навигация по коду с помощью отладчика.
Код, представленный на предыдущей иллюстрации, может выполняться отладчиком по одному оператору. Для этого можно нажимать клавишу F10 или F11 (так как здесь нет вызова метода, результат выполнения обеих команд будет одинаковым).
Когда отладчик приостанавливает выполнение, можно проверить переменные и стеки вызовов, чтобы разобраться в происходящем. Находятся ли значения в тех диапазонах, которые вы ожидали увидеть? Выполняются ли вызовы в правильном порядке?
Наведите курсор на переменную для просмотра ее текущего значения и ссылок. Если отображается значение, которое вы не ожидали увидеть, возможно, в предыдущем или вызывающем коде имеется ошибка. Более подробные сведения об отладке см. в статье об использовании отладчика.
Кроме того, Visual Studio выводит на экран окно средств диагностики, где можно наблюдать за загрузкой ЦП и использованием памяти приложением в динамике по времени. В дальнейшем в процессе разработки приложения эти средства можно применять для выявления случаев непредвиденно высокой загрузки ЦП или чрезмерного выделения памяти. Это окно можно использовать в сочетании с окном Контрольные значения и точками останова, чтобы определить причину непредвиденно интенсивного использования или неосвобожденных ресурсов. Дополнительные сведения см. в статье Обзор возможностей профилирования.
Читайте также: