Мы используем куки, чтобы пользоваться сайтом было удобно.
Хорошо
to the top
close form

Заполните форму в два простых шага ниже:

Ваши контактные данные:

Шаг 1
Поздравляем! У вас есть промокод!

Тип желаемой лицензии:

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

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

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

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

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

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


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

Вебинар: Базовые сценарии интеграции SAST решения в legacy-проект на примере PVS-Studio - 18.04

>
>
>
Логические выражения в C, C++, C# и Jav…

Логические выражения в C, C++, C# и Java. Как ошибаются профессионалы

11 Апр 2016

Логическое выражение в программировании - конструкция языка программирования, результатом вычисления которой является "истина" или "ложь". Во многих книгах по программированию, предназначенных для изучения языка "с нуля", приводятся возможные операции над логическими выражениями, с которыми сталкивался каждый начинающий разработчик. В этой статье я не буду рассказывать, что оператор 'И' приоритетнее оператора 'ИЛИ'. Я расскажу о распространённых ошибках в простых условных выражениях, состоящих всего из трёх операторов, и покажу, как можно проверить свой код с помощью построения таблиц истинности. Описанные ошибки делают разработчики таких известных проектов как FreeBSD, Microsoft ChakraCore, Mozilla Thunderbird, LibreOffice и многих других.

0390_BooleanExpressionPro_ru/image1.png

Введение

Я занимаюсь разработкой статического анализатора кода для языков C/C++/C# - PVS-Studio. В моей работе приходится много сталкиваться с открытым и закрытым кодом разных проектов. Часто результатом такой работы являются статьи о проверке open source проектов, содержащие описание найденных ошибок и недочётов. После просмотра большого объёма кода начинаешь замечать различные паттерны ошибок, которые допускают программисты. Так, мой коллега Андрей Карпов писал статью про эффект последней строки после нахождения большого количества ошибок, допущенных в последних фрагментах однотипного кода.

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

  • != || !=
  • == || !=
  • == && ==
  • == && !=

Всего таких условных выражений можно записать 6 штук, но 4 из них являются ошибочными: два являются всегда истинным или ложным; в двух результат всего выражения не зависит от результата входящего в него подвыражения.

Для доказательства неверного результата выражения я буду строить таблицу истинности в каждом примере; также я приведу для каждого примера по одному фрагменту кода из открытого проекта. В этой статье будет упомянут и тернарный оператор '?:', который имеет почти самый низкий приоритет из всех операторов, но очень много разработчиков не знают об этом.

Т.к. чаще всего я встречал неправильные условные выражения при проверке результата разных функций, код возврата которых сравнивают с кодами ошибок, то в приводимых далее синтетических примерах я буду использовать переменную с именем err, а code1 и code2 будут константами. При этом константы code1 и code2 не равны. Значение "other codes" будет означать любые другие константы, не равные code1 и code2.

Ошибки с использованием оператора '||'

Выражение != || !=

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

if ( err != code1 || err != code2)
{
  ....
}

Далее представлена таблица истинности для этого примера кода:

0390_BooleanExpressionPro_ru/image2.png

Теперь посмотрим на реальный пример ошибки, найденной в проекте LibreOffice.

V547 Expression is always true. Probably the '&&' operator should be used here. sbxmod.cxx 1777

enum SbxDataType {
  SbxEMPTY    =  0,
  SbxNULL     =  1,
  ....
};

void SbModule::GetCodeCompleteDataFromParse(
  CodeCompleteDataCache& aCache)
{
  ....
  if( (pSymDef->GetType() != SbxEMPTY) ||          // <=
      (pSymDef->GetType() != SbxNULL) )            // <=
    aCache.InsertGlobalVar( pSymDef->GetName(),
      pParser->aGblStrings.Find(pSymDef->GetTypeId()) );
  ....
}

Выражение == || !=

Синтетический пример, в котором результат всего условного выражения не зависит от результата подвыражения (err == code1):

if ( err == code1 || err != code2)
{
  ....
}

Далее представлена таблица истинности для этого примера кода:

0390_BooleanExpressionPro_ru/image3.png

Теперь посмотрим на реальный пример ошибки, найденной в проекте FreeBSD.

V590 Consider inspecting the 'error == 0 || error != - 1' expression. The expression is excessive or contains a misprint. nd6.c 2119

int
nd6_output_ifp(....)
{
  ....
  /* Use the SEND socket */
  error = send_sendso_input_hook(m, ifp, SND_OUT,
      ip6len);
  /* -1 == no app on SEND socket */
  if (error == 0 || error != -1)           // <=
      return (error);
  ....
}

Не сильно он отличается от синтетического примера, не правда ли?

Ошибки с использованием оператора '&&'

Выражение == && ==

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

if ( err == code1 && err == code2)
{
  ....
}

Далее представлена таблица истинности для этого примера кода:

0390_BooleanExpressionPro_ru/image4.png

Теперь посмотрим на реальный пример ошибки, найденной в проекте SeriousEngine.

V547 Expression is always false. Probably the '||' operator should be used here. entity.cpp 3537

enum RenderType {
  ....
  RT_BRUSH       = 4,
  RT_FIELDBRUSH  = 8,
  ....
};

void
CEntity::DumpSync_t(CTStream &strm, INDEX iExtensiveSyncCheck)
{
  ....
  if( en_pciCollisionInfo == NULL) {
    strm.FPrintF_t("Collision info NULL\n");
  } else if (en_RenderType==RT_BRUSH &&       // <=
             en_RenderType==RT_FIELDBRUSH) {  // <=
    strm.FPrintF_t("Collision info: Brush entity\n");
  } else {
  ....
  }
  ....
}

Выражение == && !=

Синтетический пример, в котором результат всего условного выражения не зависит от результата подвыражения "err != code2":

if ( err == code1 && err != code2)
{
  ....
}

Далее представлена таблица истинности для этого примера кода:

0390_BooleanExpressionPro_ru/image5.png

Теперь посмотрим на реальный пример ошибки, найденной в проекте ChakraCore - JavaScript-движке для Microsoft Edge.

V590 Consider inspecting the 'sub[i] != '-' && sub[i] == '/'' expression. The expression is excessive or contains a misprint. rl.cpp 1388

const char *
stristr
(
  const char * str,
  const char * sub
)
{
  ....
  for (i = 0; i < len; i++)
  {
    if (tolower(str[i]) != tolower(sub[i]))
    {
      if ((str[i] != '/' && str[i] != '-') ||
            (sub[i] != '-' && sub[i] == '/')) {              / <=
           // if the mismatch is not between '/' and '-'
           break;
      }
    }
  }
  ....
}

Ошибки с использованием оператора '?:'

V502 Perhaps the '?:' operator works in a different way than it was expected. The '?:' operator has a lower priority than the '|' operator. ata-serverworks.c 166

static int
ata_serverworks_chipinit(device_t dev)
{
  ....
  pci_write_config(dev, 0x5a,
           (pci_read_config(dev, 0x5a, 1) & ~0x40) |
           (ctlr->chip->cfg1 == SWKS_100) ? 0x03 : 0x02, 1);
  }
  ....
}

В заключение хочу сказать про тернарный оператор '?:'. Его приоритет почти самый низкий среди всех операторов. Ниже только у присваивания, throw и оператора "запятая". Приведённая в примере ошибка была найдена в ядре FreeBSD. Здесь тернарным оператором воспользовались для выбора нужного флажка и чтобы написать короткий красивый код. Но приоритет оператора побитового 'ИЛИ' выше, поэтому условное выражение вычисляется не в том порядке, в каком планировал программист. Эту ошибку я тоже решил описать в этой статье, т.к. она является очень распространённой среди проверенных мною проектов.

Заключение

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

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


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

Следующие комментарии next comments
close comment form