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

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

![0390_BooleanExpressionPro_ru/image1.png](https://import.viva64.com/docx/blog/0390_BooleanExpressionPro_ru/image1.png)

## Введение

Я занимаюсь разработкой статического анализатора кода для языков C/C\+\+/C\# \- [PVS\-Studio](https://pvs-studio.ru/ru/pvs-studio/)\. В моей работе приходится много сталкиваться с открытым и закрытым кодом разных проектов\. Часто результатом такой работы являются статьи о [проверке open source проектов](https://pvs-studio.ru/ru/blog/inspections/), содержащие описание найденных ошибок и недочётов\. После просмотра большого объёма кода начинаешь замечать различные паттерны ошибок, которые допускают программисты\. Так, мой коллега Андрей Карпов писал статью про [эффект последней строки](https://pvs-studio.ru/ru/blog/posts/cpp/0260/) после нахождения большого количества ошибок, допущенных в последних фрагментах однотипного кода\.

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

* \!\= \|\| \!\=
* \=\= \|\| \!\=
* \=\= && \=\=
* \=\= && \!\=

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

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

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

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

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

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

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

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

![0390_BooleanExpressionPro_ru/image2.png](https://import.viva64.com/docx/blog/0390_BooleanExpressionPro_ru/image2.png)

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

[V547](https://pvs-studio.ru/ru/docs/warnings/v547/) Expression is always true\. Probably the '&&' operator should be used here\. sbxmod\.cxx 1777

```cpp
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\)_:

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

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

![0390_BooleanExpressionPro_ru/image3.png](https://import.viva64.com/docx/blog/0390_BooleanExpressionPro_ru/image3.png)

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

[V590](https://pvs-studio.ru/ru/docs/warnings/v590/) Consider inspecting the 'error \=\= 0 \|\| error \!\= \- 1' expression\. The expression is excessive or contains a misprint\. nd6\.c 2119

```cpp
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);
  ....
}
```

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

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

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

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

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

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

![0390_BooleanExpressionPro_ru/image4.png](https://import.viva64.com/docx/blog/0390_BooleanExpressionPro_ru/image4.png)

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

[V547](https://pvs-studio.ru/ru/docs/warnings/v547/) Expression is always false\. Probably the '\|\|' operator should be used here\. entity\.cpp 3537

```cpp
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":

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

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

![0390_BooleanExpressionPro_ru/image5.png](https://import.viva64.com/docx/blog/0390_BooleanExpressionPro_ru/image5.png)

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

[V590](https://pvs-studio.ru/ru/docs/warnings/v590/) Consider inspecting the 'sub\[i\] \!\= '\-' && sub\[i\] \=\= '/'' expression\. The expression is excessive or contains a misprint\. rl\.cpp 1388

```cpp
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](https://pvs-studio.ru/ru/docs/warnings/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

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