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

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

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

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

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

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

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


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

>
>
>
Логические выражения в 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. Подстраховать себя от таких ошибок можно путём проверки своего кода с помощью построения таблиц истинности, если есть сомнения в правильности условия, а также с помощью регулярных проверок статическими анализаторами.

Популярные статьи по теме
В мире антропоморфных животных: PVS-Studio проверил Overgrowth

Дата: 23 Июн 2022

Автор: Владислав Столяров

Недавно в сети появилась новость о том, что был открыт исходный код игры Overgrowth. Мы не смогли пройти мимо и проверили его качество с помощью PVS-Studio. Давайте же вместе посмотрим, где больше ин…
Как написать рефлексию для C++

Дата: 21 Июн 2022

Автор: Гость

C++ поистине противоречивый язык. Старый добрый С существует аж с 1972 года, С++ появился в 1985 и сохранил с ним обратную совместимость. За это время его не раз хоронили: сперва Java, теперь его пот…
Проверяем эмулятор GPCS4, или сможем ли когда-нибудь поиграть в "Bloodborne" на PC

Дата: 16 Июн 2022

Автор: Александр Куренев

Эмулятор – это приложение, способное имитировать запуск программы, предназначенной для одной платформы, на другой. Примером эмулятора является GPCS4, предназначенный для запуска игр для PS4 на PC. Не…
Новый механизм мониторинга компиляции в PVS-Studio для Windows

Дата: 14 Июн 2022

Автор: Алексей Говоров

В релизе PVS-Studio 7.18 утилита мониторинга компиляции для Windows получила новый механизм, который позволяет полностью устранить пропуски запусков компиляторов. В этой статье мы напомним, как наш а…
50 вредных советов для С++ программиста

Дата: 09 Июн 2022

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

Какую статью по C++ ни возьми, она серьёзна и требует вдумчивого чтения, желательно с кофе. А развлечься тоже хочется. Для этого и написана эта шуточная статья с вредными советами по программированию…

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

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