Для получения триального ключа
заполните форму ниже
Team License (базовая версия)
Enterprise License (расширенная версия)
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

Запросите информацию о ценах
Новая лицензия
Продление лицензии
--Выберите валюту--
USD
EUR
GBP
RUB
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

Бесплатная лицензия PVS-Studio для специалистов Microsoft MVP
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

Для получения лицензии для вашего открытого
проекта заполните, пожалуйста, эту форму
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

Мне интересно попробовать плагин на:
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

Ваше сообщение отправлено.

Мы ответим вам на


Если вы так и не получили ответ, пожалуйста, проверьте папку
Spam/Junk и нажмите на письме кнопку "Не спам".
Так Вы не пропустите ответы от нашей команды.

>
>
>
Как уменьшить вероятность ошибки на эта…

Как уменьшить вероятность ошибки на этапе написания кода. Заметка N2

29 Мар 2011

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

Введение

a0072_expressions_ru/image1.png

С предыдущей заметкой можно познакомиться здесь. В этот раз примеры ошибок будут взяты из различных известных проектов, чтобы подчеркнуть их распространенность. Ошибки, которые будут продемонстрированы, были найдены мной с помощью анализатора PVS-Studio за относительно большой промежуток времени. Практически про все я писал разработчикам и поэтому можно надеяться, что они поправлены в новых редакциях кода. Это я пишу во введении, потому что после статей мне всегда приходят письма с просьбой "напишите про найденные вами ошибки разработчикам проекта".

1. Не используйте тернарную операцию '?:' в составных выражениях

Тернарная условная операция записывается в языке Си/Си++ с помощью оператора '?:'. Операция, возвращающая свой второй или третий операнд в зависимости от значения логического выражения, заданного первым операндом. Пример:

int minValue = A < B ? A : B;

Тернарная операция имеет очень низкий приоритет (см. таблицу). Про это часто забывают и именно из-за этого тернарная операция крайне опасна.

a0072_expressions_ru/image2.png

Рисунок 1 - Операции языка Си/Си++, в порядке снижения приоритета

Обратите внимание, что операция '?:' имеет более низкий приоритет, чем сложение, умножение, оператора побитового ИЛИ и так далее. Рассмотрим код:

int Z = X + (A == B) ? 1 : 2;

Этот код работает не так, как может показаться на первый взгляд. Программист, скорее всего, хотел сложить значение X с числом 1 или с числом 2, в зависимости от условия (A == B). Но на самом деле условием является выражение "X + (A == B)". Фактически, здесь написано:

int Z = (X + (A == B)) ? 1 : 2;

А хотелось:

int Z = X + (A == B ? 1 : 2);

Первая мысль – так ведь надо знать приоритет операций. А программисты их и знают, но уж очень коварна эта тернарная операция! Ошибки с ней допускают не только новички, но и матёрые программисты. Её нередко можно встретить даже в самом качественном коде. Вот пара примеров.

a0072_expressions_ru/image3.png

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

dgInt32 CalculateConvexShapeIntersection (...)
{
  ...
  den = dgFloat32 (1.0e-24f) *
        (den > dgFloat32 (0.0f)) ? 
           dgFloat32 (1.0f) : dgFloat32 (-1.0f);
  ...
}
a0072_expressions_ru/image4.png

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

static const int kClientEdgeThickness;
int height() const;
bool ShouldShowClientEdge() const;
void CustomFrameView::PaintMaximizedFrameBorder(gfx::Canvas* canvas) {
  ...
  int edge_height = titlebar_bottom->height() -
                    ShouldShowClientEdge() ? kClientEdgeThickness : 0;
  ...
}
a0072_expressions_ru/image5.png

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

#define FILE_ATTRIBUTE_NORMAL    0x00000080
#define FILE_FLAG_NO_BUFFERING    0x20000000
vm_file* vm_file_fopen(...)
{
  ...
  mds[3] = FILE_ATTRIBUTE_NORMAL |
           (islog == 0) ? 0 : FILE_FLAG_NO_BUFFERING;
  ...
}

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

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

Конечно оператор '?:' является синтаксическим сахаром и его практически всегда можно заменить на if. К редким исключениям относится такие задачи, как инициализация ссылки:

MyObject &ref = X ? A : B;

Конечно, и с этим проблем нет, но создание ссылки на A или B без оператора '?:' будет гораздо многословней:

MyObject *tmpPtr;
If (X)
  tmpPtr = &A;
else
  tmpPtr = &B;
MyObject &ref = *tmpPtr;

Итак, отказываться от оператора '?:' не рационально. Но и ошибиться с ним просто. Поэтому лично я выработал для себя следующее правило. Результат '?:' должен сразу куда-то помещаться и не сочетаться с другими действиями. То есть слева от условия оператора '?:' должна стоять операция присваивания. Вернемся к первоначальному примеру:

int Z = X + (A == B) ? 1 : 2;

Я предлагаю писать так:

int Z = X;
Z += A == B ? 1 : 2;

В случае примера, относящегося к IPP Samples, я бы написал так:

mds[3] = FILE_ATTRIBUTE_NORMAL;
mds[3] |= (islog == 0) ? 0 : FILE_FLAG_NO_BUFFERING;

С рекомендацией можно не согласиться. Не буду её отстаивать. Например, мне не очень нравится, что вместо одной строки мы получаем две или более. Неплохой альтернативой может быть обязательное взятие оператора '?:' в скобки. Я ставлю основной задачей показать паттерны ошибок. Выбор паттерна защиты от ошибок зависит от предпочтений программиста.

2. Не стесняйтесь использовать скобки

Почему-то так повелось, что использование лишних скобок при программировании на Си/Си++ считается неким постыдным действием. Возможно, это идёт от того, что вопрос про приоритет операций очень любят задавать на собеседовании. И у человека подсознательно откладывается стремление всегда максимально использовать механизм приоритетов. Ведь если он поставит лишние скобки, то вдруг кто-то подумает что он новичок, а не истинный джедай.

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

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

a0072_expressions_ru/image6.png

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

#define SVF_CASTAI 0x00000010
char *ClientConnect(...) {
  ...
  if ( !ent->r.svFlags & SVF_CASTAI ) {
  ...
}
a0072_expressions_ru/image7.png

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

static SDL_Surface * GFX_SetupSurfaceScaled(Bit32u sdl_flags, 
                                            Bit32u bpp) {
  ...
  if (!sdl.blit.surface || (!sdl.blit.surface->flags&SDL_HWSURFACE)) {
  ...
}

Ну и еще один пример из 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

#define FILE_ATTRIBUTE_DIRECTORY 0x00000010
bool GetPlatformFileInfo(PlatformFile file, PlatformFileInfo* info) {
  ...
  info->is_directory =
    file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY != 0;
  ...
}

Выражения просты. Разработчики молодцы. А ошибки всё равно есть. Так что использование скобок в скользких местах лишним не будет.

Наверное, стоит поступать так. Если операции просты и привычны, то дополнительные скобки не нужны. Примеры:

if (A == B && X != Y)
if (A - B < Foo() * 2)

А вот если используются более редкие операторы (~, ^, &, |, <<, >>, ?:), то явные скобки не повредят. Это и упростит чтение кода и защит от потенциальной ошибки. Примеры:

If ( ! (A & B))
x = A | B | (z < 1 ? 2 : 3);

Не стесняться использовать скобки при использовании редких операций поможет и для рассмотренного ранее оператора "?:". Как именно лучше поступить с "?:" - дело вкуса. Мне более нравится вариант с упрощением.

Заключение

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

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

Программист, который в будущем будет читать ваш код со скобками, только скажет спасибо за это.

Популярные статьи по теме
PVS-Studio ROI

Дата: 30 Янв 2019

Автор: Андрей Карпов

Время от времени нам задают вопрос, какую пользу в денежном эквиваленте получит компания от использования анализатора PVS-Studio. Мы решили оформить ответ в виде статьи и привести таблицы, которые по…
Характеристики анализатора PVS-Studio на примере EFL Core Libraries, 10-15% ложных срабатываний

Дата: 31 Июл 2017

Автор: Андрей Карпов

После большой статьи про проверку операционной системы Tizen мне было задано много вопросов о проценте ложных срабатываний и о плотности ошибок (сколько ошибок PVS-Studio выявляет на 1000 строк кода)…
Статический анализ как часть процесса разработки Unreal Engine

Дата: 27 Июн 2017

Автор: Андрей Карпов

Проект Unreal Engine развивается - добавляется новый код и изменятся уже написанный. Неизбежное следствие развития проекта - появление в коде новых ошибок, которые желательно выявлять как можно раньш…
Зло живёт в функциях сравнения

Дата: 19 Май 2017

Автор: Андрей Карпов

Возможно, читатели помнят мою статью под названием "Эффект последней строки". В ней идёт речь о замеченной мной закономерности: ошибка чаще всего допускается в последней строке однотипных блоков текс…
Бесплатный PVS-Studio для тех, кто развивает открытые проекты

Дата: 22 Дек 2018

Автор: Андрей Карпов

В канун празднования нового 2019 года команда PVS-Studio решила сделать приятный подарок всем контрибьюторам open-source проектов, хостящихся на GitHub, GitLab или Bitbucket. Им предоставляется возмо…
PVS-Studio для Java

Дата: 17 Янв 2019

Автор: Андрей Карпов

В седьмой версии статического анализатора PVS-Studio мы добавили поддержку языка Java. Пришло время немного рассказать, как мы начинали делать поддержку языка Java, что у нас получилось и какие дальн…
Как PVS-Studio оказался внимательнее, чем три с половиной программиста

Дата: 22 Окт 2018

Автор: Андрей Карпов

PVS-Studio, как и другие статические анализаторы кода, часто выдаёт ложные срабатывания. Но не стоит спешить считать странные срабатывания ложными. Это короткая история о том, как PVS-Studio вновь ок…
Как и почему статические анализаторы борются с ложными срабатываниями

Дата: 20 Мар 2017

Автор: Андрей Карпов

В своей предыдущей статье я писал, что мне не нравится подход, при котором статические анализаторы кода оцениваются с помощью синтетических тестов. В статье приводился пример, воспринимаемый анализат…
Эффект последней строки

Дата: 31 Май 2014

Автор: Андрей Карпов

Я изучил множество ошибок, возникающих в результате копирования кода. И утверждаю, что чаще всего ошибки допускают в последнем фрагменте однотипного кода. Ранее я не встречал в книгах описания этого …
Главный вопрос программирования, рефакторинга и всего такого

Дата: 14 Апр 2016

Автор: Андрей Карпов

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

Комментарии (0)

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