![]() |
Модераторы: Daevaorn |
![]() ![]() ![]() |
|
Thunderbolt |
|
||||||||||||||||||||||||||||||||||
![]() DevRel ![]() Профиль Группа: Участник Сообщений: 122 Регистрация: 7.11.2007 Где: Тула Репутация: 10 Всего: 16 |
Аннотация
![]() С предыдущей заметкой можно познакомиться здесь. В этот раз примеры ошибок будут взяты из различных известных проектов, чтобы подчеркнуть их распространенность. Ошибки, которые будут продемонстрированы, были найдены мной с помощью анализатора PVS-Studio за относительно большой промежуток времени. Практически про все я писал разработчикам и поэтому можно надеяться, что они поправлены в новых редакциях кода. Это я пишу во введении, потому что после статей мне всегда приходят письма с просьбой "напишите про найденные вами ошибки разработчикам проекта". Это вторая статья о том, как можно избежать ряда ошибок еще на этапе написания кода. В предыдущей заметке уже упоминался совет избегать множества вычислений в одном выражении. Однако, этот вопрос требует более пристального внимания. Рассмотрим опасность сложных условий, и как можно предупредить многие логические ошибки. Введение 1. Не используйте тернарную операцию '?:' в составных выражениях Тернарная условная операция записывается в языке Си/Си++ с помощью оператора '?:'. Операция, возвращающая свой второй или третий операнд в зависимости от значения логического выражения, заданного первым операндом. Пример:
Тернарная операция имеет очень низкий приоритет (см. таблицу). Про это часто забывают и именно из-за этого тернарная операция крайне опасна. ![]() Рисунок 1 - Операции языка Си/Си++, в порядке снижения приоритета Обратите внимание, что операция '?:' имеет более низкий приоритет, чем сложение, умножение, оператора побитового ИЛИ и так далее. Рассмотрим код:
Этот код работает не так, как может показаться на первый взгляд. Программист, скорее всего, хотел сложить значение X с числом 1 или с числом 2, в зависимости от условия (A == B). Но на самом деле условием является выражение "X + (A == B)". Фактически, здесь написано:
А хотелось:
Первая мысль - так ведь надо знать приоритет операций. А программисты их и знают, но уж очень коварна эта тернарная операция! Ошибки с ней допускают не только новички, но и матёрые программисты. Её нередко можно встретить даже в самом качественном коде. Вот пара примеров. ![]() V502 Perhaps the '?:' operator works in a different way than it was expected. The '?:' operator has a lower priority than the '*' operator. physics dgminkowskiconv.cpp 1061
![]() V502 Perhaps the '?:' operator works in a different way than it was expected. The '?:' operator has a lower priority than the '-' operator. views custom_frame_view.cc 400
![]() V502 Perhaps the '?:' operator works in a different way than it was expected. The '?:' operator has a lower priority than the '|' operator. vm vm_file_win.c 393
Как видите, ошибки данного типа заслуживают внимания. И я не случайно выделил их описание в отдельный пункт. Они весьма и весьма распространены. Можно привести другие примеры, но все они однотипны. Избежать подобных ошибок можно, если не стремиться поместить несколько операций в одну строку кода. А если и помещать, то не жадничать поставить дополнительные скобки. Про скобки мы поговорим ниже. Сейчас попробуем просто предотвратить потенциальные ошибки при использовании '?:'. Конечно оператор '?:' является синтаксическим сахаром и его практически всегда можно заменить на if. К редким исключениям относится такие задачи, как инициализация ссылки:
Конечно, и с этим проблем нет, но создание ссылки на A или B без оператора '?:' будет гораздо многословней:
Итак, отказываться от оператора '?:' не рационально. Но и ошибиться с ним просто. Поэтому лично я выработал для себя следующее правило. Результат '?:' должен сразу куда-то помещаться и не сочетаться с другими действиями. То есть слева от условия оператора '?:' должна стоять операция присваивания. Вернемся к первоначальному примеру:
Я предлагаю писать так:
В случае примера, относящегося к IPP Samples, я бы написал так:
С рекомендацией можно не согласиться. Не буду её отстаивать. Например, мне не очень нравится, что вместо одной строки мы получаем две или более. Неплохой альтернативой может быть обязательное взятие оператора '?:' в скобки. Я ставлю основной задачей показать паттерны ошибок. Выбор паттерна защиты от ошибок зависит от предпочтений программиста. 2. Не стесняйтесь использовать скобки Почему-то так повелось, что использование лишних скобок при программировании на Си/Си++ считается неким постыдным действием. Возможно, это идёт от того, что вопрос про приоритет операций очень любят задавать на собеседовании. И у человека подсознательно откладывается стремление всегда максимально использовать механизм приоритетов. Ведь если он поставит лишние скобки, то вдруг кто-то подумает что он новичок, а не истинный джедай. Я даже встречал на просторах интернета обсуждение, где человек категорично утверждал, что использование лишних скобок - это плохой тон. А если человек не уверен как будет вычислено выражение, то ему надо учиться, а не программы писать. К сожалению, не смог найти эту дискуссию, но я не разделяю подобные точки зрения. Знать приоритеты, конечно, надо, но если в выражении используются разнородные операции, то лучше подстраховывать себя скобками. Это не только защитит от потенциальных ошибок, но и сделает код более легко читаемым для других разработчиков. Ошибки с путаницей приоритетов также возникают не только у новичков, но и у профессионалов. При этом выражение не обязательно должно быть сильно запутанным и длинным. Ошибку можно допустить и в относительно простых выражениях. Рассмотрим некоторые примеры. ![]() V564 The '&' operator is applied to bool type value. You've probably forgotten to include parentheses or intended to use the '&&' operator. game g_client.c 1534
![]() V564 The '&' operator is applied to bool type value. You've probably forgotten to include parentheses or intended to use the '&&' operator. dosbox sdlmain.cpp 519
Ну и еще один пример из Chromium: V564 The '&' operator is applied to bool type value. You've probably forgotten to include parentheses or intended to use the '&&' operator. base platform_file_win.cc 216
Выражения просты. Разработчики молодцы. А ошибки всё равно есть. Так что использование скобок в скользких местах лишним не будет. Наверное, стоит поступать так. Если операции просты и привычны, то дополнительные скобки не нужны. Примеры:
А вот если используются более редкие операторы (~, ^, &, |, <<, >>, ? ![]()
Не стесняться использовать скобки при использовании редких операций поможет и для рассмотренного ранее оператора "?:". Как именно лучше поступить с "?:" - дело вкуса. Мне более нравится вариант с упрощением. Заключение Пишите простой и понятный код. Разбивая длинные и сложные выражения на несколько строк, вы получаете более длинный код. Но такой код бывает намного проще понять и прочитать. В нем меньше вероятность допустить ошибку. Не бойтесь завести лишнюю переменную. Компилятор отлично всё оптимизирует. Не жадничайте на скобках в выражениях, где используются редкие операторы или где смешиваются битовые и логические операции. Программист, который в будущем будет читать ваш код со скобками, только скажет спасибо за это. --------------------
Карпов Андрей, DevRel в PVS-Studio. |
||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||
![]() ![]() ![]() |
Правила форума "С++:Общие вопросы" | |
|
Добро пожаловать!
Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Earnest Daevaorn |
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей) | |
0 Пользователей: | |
« Предыдущая тема | C/C++: Общие вопросы | Следующая тема » |
|
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности Powered by Invision Power Board(R) 1.3 © 2003 IPS, Inc. |