Мы часто видим предупреждения компилятора об участках кода, которые имеют потенциальные проблемы или плохой стиль. Иногда они указывают на код, который действительно не работает, поэтому не игнорируйте их.
Вы, наверняка, видели
Не игнорируйте предупреждения компилятора
Конечно, эти предупреждения не могут не раздражать; к тому же, чаще всего они возникают для идеально работающего кода, который точно не содержит багов.
Но будьте уверены, что одно из нескольких тысяч предупреждений компилятора действительно имеет смысл. Иногда мы пишем код, который компилируется, но
Примите политику «без предупреждений»
Если уж совсем честно, то есть только две вещи, которые можно сделать с предупреждениями компилятора — игнорировать их или избавиться от них. Игнорировать их — значит выбросить в мусорное ведро инструмент, который поможет предотвратить баги. Согласитесь ли вы смотреть сквозь пальцы на наличие
Вы можете сказать, что если предупреждений компилятора всего несколько — вы вполне сможете с этим жить. Тут вот какая штука: вы, наверное, проверяете свой код относительно часто (по крайней мере, мы на это надеемся). Это значит, что вам приходится часто компилировать, а это влечет за собой предупреждения компилятора. Вы станете обращать на них внимание. Возможно, вы все еще будете уделять им внимание, если получите 6 предупреждений вместо 5; но заметите ли вы
Избавиться от предупреждений — единственный приемлемый и безопасный для психики вариант.
Измените свой код, чтобы избавиться от предупреждений
Хотя иногда нам и хочется, чтобы компилятор просто промолчал о конкретном предупреждении, лучше просто изменить код. Если код не понятен компилятору, то есть все шансы, что он не будет понятен и человеку тоже.
Прояснить свои намерения в коде часто достаточно, чтобы заставить замолчать компилятор. Некоторые компиляторы могут дать подсказку, как решить определенные предупреждения. Давайте посмотрим на часто появляющиеся предупреждения о присваивании в условном контексте:
Для Clang это выглядит так:
Второй случай, который лежит в основе этого предупреждения: иногда мы пишем a = b, когда мы имели в виду a == b.
В то время как другие компиляторы просто предупреждают, что написанное нами задание выглядит достаточно странно в этом участке кода, CLANG услужливо пытается угадать, что же мы могли иметь в виду.
Первый указатель просто говорит нам, как исправить предупреждение, если задание на самом деле так и было задумано. У GCC есть такое же предупреждение и предложение исправить, но без предоставления альтернативы:
Многие отдают предпочтение CLANG, так как он позволяет нам предположить, каково правильное решение. Это гораздо лучше, поскольку дает возможность найти в коде баг, чего не случилось бы, прими мы автоматически все, что предлагает компилятор.
Даже если присваивание действительно было запланировано, принятие предложения компилятора и добавление пары скобок могут быть неправильным решением.
С точки зрения чистого кода, мы должны разделить присваивание и условие. Гораздо удобнее иметь по одной строке для каждой задачи, то есть, по сути, применять Принцип Единой Ответственности на построчном базисе:
Не исправляйте предупреждений компилятора автоматически, вносите исправления только обдуманно.
Скажите компилятору, предупреждения какого типа важны для вас
Существует несколько способов, как работать с компилятором и не получать предупреждений. Естественно, написание кода, который понятен даже бедняге компилятору — лучшее решение. Однако есть также возможность сказать компилятору, какие предупреждения вас интересуют, а какие — нет.
Флаги компилятора
Каждый известный нам компилятор обеспечивает возможность выбора предупреждений, которые вы хотите видеть. Можно отключить различные группы предупреждений, а иногда можно переназначить отдельные предупреждения на другой уровень предупреждений. Обычно эти возможности предоставляются как параметры командной строки в настройках IDE. Это значит, что у вас может быть единственное место — предпочтительно ваш билд скрипт — где вы можете эти настройки применить.
Какие флаги предупреждений использовать? Это зависит частично от компилятора, поскольку разные компиляторы эмитируют разные предупреждения, и некоторые из них могут быть не просто ошибочными, а бредовыми.
Некоторые предупреждения могут быть слишком педантичными на ваш вкус или для вашего стиля написания кода, хотя нам до сих пор не встречались предупреждения, не имеющие под собой никаких оснований. (На наш взгляд вообще лучше просматривать все предупреждения, чтобы не пропустить важное).
Стандартные флаги для большинства предупреждений — это Wall, Wpedantic, Wextra (многие флаги компилятора, касающиеся предупреждений, начинаются с W).
Если вы только начинаете применять политику «без предупреждений» для своего проекта, вы, скорее всего, получаете сотни или даже тысячи предупреждений, если они все включены. Начните с более низкого уровня предупреждений. Зафиксируйте наиболее жесткие предупреждения и постепенно наберите свой уровень предупреждений.
Если вы подвержены моментам лени, или у вас есть коллеги, выступающие против политики «без предупреждений», то отключить их совсем будет не так то просто. У
Чтобы избежать такой траты времени, вы можете усилить политику «без предупреждений», переключив предупреждения в ошибки. Таким образом, ошибки будет сложно проигнорировать — иначе билд просто провалится. Так можно поступить с отдельными предупреждениями, а также со всеми предупреждениями одновременно. Установите соответствующий флаг -Werror для Clang и GCC и /WX для MSVC.
Директивы Pragma
Компиляторы часто используют особые директивы #pragma, позволяющие включить или отключить конкретные предупреждения кода. Эти директивы следует рассматривать как временное решение, потому что у них есть свои недостатки:
- Отключение предупреждений с помощью #pragma заглушает компилятор для всего оставшегося юнита. Если вы хотите отключить это предупреждение только однократно, обязательно включите его прямо после строки кода с вопросом. Включение #pragma для файла заголовка и решение не включать предупреждения снова заглушит компилятор для каждого источника, имеющего заголовок, а также для каждого заголовка после #pragma
- Директивы #pragma для предупреждений не портативны. Идентификаторы для каждого отдельного предупреждения варьируются для разных компиляторов, также как и формат #pragma. Иногда компиляторы выдают предупреждения о неизвестных #pragma — и вы определенно не захотите писать прописывать в GCC предупреждении #pragma, что ему стоит игнорировать эти MSVC предупреждения. Заключить их в #ifdefs явно не самое лучшее решение.
Бывают случаи, когда вам не подходит использование #pragma.
В качестве примера можно привести заголовки сторонних библиотек, которые вы изменить не можете, а компилятор на них жалуется. Другой пример — с однажды написанным встраиваемым языком: он использовал перегрузку операторов необычным способом, который игнорировал встроенный приоритет операторов
Компилятор услужливо предлагал добавить дополнительные скобки. Это могло бы быть верным решением, будь операторы применяемы к числовым значениям. Чтобы код DSL читался, в таком случае требуется заглушить предупреждение, не трогая код, здесь поможет отключение предупреждения с помощью #pragma в комплексе с поясняющим комментарием.
Вместо заключения
Вы можете настроить компилятор, сообщив ему, какие предупреждения вам интересны, а какие — нет; используйте для этого аргументы командной строки или, если потребуется, директивы #pragmas. Старайтесь быть максимально строгими, не добавляйте слишком много исключений. Это означает осторожное использование #pragmas и отклонений от ваших стандартных аргументов командной строки.
Комментарии (14)
Lau
18.05.2016 13:50Прочитав первые два абзаца не на шутку взволновался, подумал что сейчас расскажут, что ошибки компилятора только мешают и вот список тех, что можно смело игнорировать. Оказалось зря.
Коротких путей нет — хотите сделать код стабильнее — вычитайте все предупреждения и исправьте код.Ivan_83
18.05.2016 14:20Ошибки в компиляторах — отдельная больная тема.
Всегда считал что компиляторы пишут умные люди и тестируют и ошибок там нет, но за прошедший месяц нашёл там пару косяков: один приводил к тому, что инстрикт возвращал неверное значение, а другой к падению производительности в 5-10 раз при использовании другого инстрикта.
elmm
18.05.2016 15:08Править свой код, чтоб не было ворнингов — золотое правило, но порой берёшь какую-то библиотеку, а из неё ворнинги лезут из всех дыр. И вроде работает, но за массой чужих ворнингов, перестаёшь свои замечать, а дальше ситуация только усугубляется.
DarkEld3r
18.05.2016 16:18+1Некоторые компиляторы умеют отключать предупреждения для библиотечных хедеров.
monah_tuk
18.05.2016 17:48Да не некоторые, а, пожалуй, почти все. Только делается это не стандартно. Ну и чуточку более корректно: они могут отключать предупреждения для участков кода, соответственно заголовочник библиотеки подключается, в наиболее удобном варианте, через враппер. Например, мой
libusb_wrapper.h
для gcc:
#pragma once #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpedantic" #include <libusb.h> #pragma GCC diagnostic pop
encyclopedist
19.05.2016 23:50Для GCC и Clang достаточно указать директорию с помощью
-isystem <DIR>
вместо-I <DIR>
Andrey2008
18.05.2016 17:12-1Всё правильно. Предупреждения компилятора следует слушать и писать код так, чтобы их не было. Это первый шаг. Когда с этим разобрались, но хочется совершенства, встаёт вопрос, что ещё можно сделать для повышения качества и надежности кода. Тогда можно сделать второй шаг — начать использовать статические анализаторы кода. По сути эти те-же предупреждения компилятора, но только более продвинутые.
А быть может, достаточно просто использовать возможности компилятора Visual C++ (или FxCop)? Нет, не достаточно: Демонстрация возможностей анализатора PVS-Studio на примере открытых проектов компании Microsoft.
monah_tuk
18.05.2016 17:53Тема — прописная истина. Что не мешает ей быть чертовски актуальной. С другой стороны, в коде без ворнингов можно использовать следующий трюк: все знают, что временами код обрастает всякими TODO/FIXME и так далее, и живут они потом там вечно. А если вместо TODO вставить:
#warning TODO: remove this ugly comment
то при отсутствии других ворнингов это будет назойливо мозолить глаза и шансов на выпил причин данного TODO будет больше.
laphroaig
19.05.2016 02:37Как тимлид решил проблему радикально: -Wall -pedantic -Wextra -Werror
Здесь ключевое слово -Werror. А если…
4144
28.05.2016 16:19Для кросс компиляторного использования #pragma, есть такое ключевое слово _Pragma.
Это аналог #pragma, но его можно использовать в макросах.
Т.е. можно объявить свою прагму, которая будет на пример работать только для clang, а gcc её будет игнорировать.
Примерно так:
#ifdef __clang__
#define PRAGMACLANG(str) _Pragma(#str)
#else
#define PRAGMACLANG(str)
#endif
И теперь в коде для заглушения какого-то предупреждения clang, можно написать:
PRAGMACLANG(clang diagnostic push)
PRAGMACLANG(clang diagnostic ignored "-Wsomething"
… здесь какой-то код…
PRAGMACLANG(clang diagnostic pop)
Также можно учитывать версию компиляторов, включенный стандарт C/C++ кода и т.д.
Ivan_83
Для clang есть ещё дополнительно: -Weverything
но я после него некоторые варнинги отключаю через -Wno-… потому как это уже борьба с ветряными мельницами начинается:
-Wno-gnu-zero-variadic-macro-arguments;-Wno-variadic-macros;-Wno-padded;-Wno-packed;-Wno-unused-macros;-Wno-reserved-id-macro;-Wno-date-time