Предупреждения компилятора в С++: принять нельзя отказать

в 6:24, , рубрики: C#, c++, Блог компании Cloud4Y, ит-инфраструктура, компилятор, Облачные вычисления, облачные технологии, Программирование, С++, хостинг, хостинг-провайдер

Зашел у нас как-то в Cloud4Y разговор о программировании в облаке и о том, какой язык программирования можно по праву считать самым «облачным». Долго ли котротко ли, дошли до обсуждения ошибок копмиляторов — о их двоякой натуре: и проигнорировать все нельзя, и обращать внимание на каждый — умом тронуться недолго. Сегодня мы представим вам небольшой сценарий обращения с предупреждениями компилятора, который позволит не пропустить грубую ошибку и сохранит нервную систему в порядке.

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

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

Не игнорируйте предупреждения компилятора

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

Но будьте уверены, что одно из нескольких тысяч предупреждений компилятора действительно имеет смысл. Иногда мы пишем код, который компилируется, но что-то работает не так, как мы предполагали. Так как же, во имя Хабра, определить, какое именно предупреждение указывает на реальную ошибку? Как отличить его от сотен других, при которых код действителен? Неужели нужно тратить столько времени на то, чтобы прочитать их все? Это практически невозможно.
Примите политику «без предупреждений»

Если уж совсем честно, то есть только две вещи, которые можно сделать с предупреждениями компилятора — игнорировать их или избавиться от них. Игнорировать их — значит выбросить в мусорное ведро инструмент, который поможет предотвратить баги. Согласитесь ли вы смотреть сквозь пальцы на наличие одной-двух серьезных ошибок? Наверное, нет.

Вы можете сказать, что если предупреждений компилятора всего несколько — вы вполне сможете с этим жить. Тут вот какая штука: вы, наверное, проверяете свой код относительно часто (по крайней мере, мы на это надеемся). Это значит, что вам приходится часто компилировать, а это влечет за собой предупреждения компилятора. Вы станете обращать на них внимание. Возможно, вы все еще будете уделять им внимание, если получите 6 предупреждений вместо 5; но заметите ли вы 11-е? 20-е? 52-е?
Избавиться от предупреждений — единственный приемлемый и безопасный для психики вариант.

Измените свой код, чтобы избавиться от предупреждений

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

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

image
Для Clang это выглядит так:

image
Второй случай, который лежит в основе этого предупреждения: иногда мы пишем a = b, когда мы имели в виду a == b.

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

Первый указатель просто говорит нам, как исправить предупреждение, если задание на самом деле так и было задумано. У GCC есть такое же предупреждение и предложение исправить, но без предоставления альтернативы:

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

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

С точки зрения чистого кода, мы должны разделить присваивание и условие. Гораздо удобнее иметь по одной строке для каждой задачи, то есть, по сути, применять Принцип Единой Ответственности на построчном базисе:

image
Не исправляйте предупреждений компилятора автоматически, вносите исправления только обдуманно.
Скажите компилятору, предупреждения какого типа важны для вас

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

Флаги компилятора

Каждый известный нам компилятор обеспечивает возможность выбора предупреждений, которые вы хотите видеть. Можно отключить различные группы предупреждений, а иногда можно переназначить отдельные предупреждения на другой уровень предупреждений. Обычно эти возможности предоставляются как параметры командной строки в настройках IDE. Это значит, что у вас может быть единственное место — предпочтительно ваш билд скрипт — где вы можете эти настройки применить.

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

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

Стандартные флаги для большинства предупреждений — это Wall, Wpedantic, Wextra (многие флаги компилятора, касающиеся предупреждений, начинаются с W).
Если вы только начинаете применять политику «без предупреждений» для своего проекта, вы, скорее всего, получаете сотни или даже тысячи предупреждений, если они все включены. Начните с более низкого уровня предупреждений. Зафиксируйте наиболее жесткие предупреждения и постепенно наберите свой уровень предупреждений.

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

Чтобы избежать такой траты времени, вы можете усилить политику «без предупреждений», переключив предупреждения в ошибки. Таким образом, ошибки будет сложно проигнорировать — иначе билд просто провалится. Так можно поступить с отдельными предупреждениями, а также со всеми предупреждениями одновременно. Установите соответствующий флаг -Werror для Clang и GCC и /WX для MSVC.

Директивы Pragma

Компиляторы часто используют особые директивы #pragma, позволяющие включить или отключить конкретные предупреждения кода. Эти директивы следует рассматривать как временное решение, потому что у них есть свои недостатки:

  • Отключение предупреждений с помощью #pragma заглушает компилятор для всего оставшегося юнита. Если вы хотите отключить это предупреждение только однократно, обязательно включите его прямо после строки кода с вопросом. Включение #pragma для файла заголовка и решение не включать предупреждения снова заглушит компилятор для каждого источника, имеющего заголовок, а также для каждого заголовка после #pragma
  • Директивы #pragma для предупреждений не портативны. Идентификаторы для каждого отдельного предупреждения варьируются для разных компиляторов, также как и формат #pragma. Иногда компиляторы выдают предупреждения о неизвестных #pragma — и вы определенно не захотите писать прописывать в GCC предупреждении #pragma, что ему стоит игнорировать эти MSVC предупреждения. Заключить их в #ifdefs явно не самое лучшее решение.

Бывают случаи, когда вам не подходит использование #pragma.

В качестве примера можно привести заголовки сторонних библиотек, которые вы изменить не можете, а компилятор на них жалуется. Другой пример — с однажды написанным встраиваемым языком: он использовал перегрузку операторов необычным способом, который игнорировал встроенный приоритет операторов C++.

Компилятор услужливо предлагал добавить дополнительные скобки. Это могло бы быть верным решением, будь операторы применяемы к числовым значениям. Чтобы код DSL читался, в таком случае требуется заглушить предупреждение, не трогая код, здесь поможет отключение предупреждения с помощью #pragma в комплексе с поясняющим комментарием.

Вместо заключения

Вы можете настроить компилятор, сообщив ему, какие предупреждения вам интересны, а какие — нет; используйте для этого аргументы командной строки или, если потребуется, директивы #pragmas. Старайтесь быть максимально строгими, не добавляйте слишком много исключений. Это означает осторожное использование #pragmas и отклонений от ваших стандартных аргументов командной строки.

P. S. Иногда ни один из перечисленных способов не является реалистичным решением. Тогда просто используйте для компилятора команду shut up.

Автор: Cloud4Y

Источник

Поделиться новостью

* - обязательные к заполнению поля