Мы используем куки, чтобы пользоваться сайтом было удобно.
Хорошо
to the top
>
>
>
V610. Undefined behavior. Check the shi…
menu mobile close menu
Проверка проектов
Сообщения PVS-Studio
Диагностики общего назначения (General Analysis, C++)
Диагностики общего назначения (General Analysis, C#)
Диагностики общего назначения (General Analysis, Java)
Микрооптимизации (C++)
Диагностика 64-битных ошибок (Viva64, C++)
Реализовано по запросам пользователей (C++)
Cтандарт MISRA
Стандарт AUTOSAR
Стандарт OWASP (C++)
Стандарт OWASP (C#)
Проблемы при работе анализатора кода
Дополнительная информация
toggle menu Оглавление

V610. Undefined behavior. Check the shift operator.

06 Дек 2012

Анализатор обнаружил операцию сдвига, которая приводит к неопределенному или к неуточненному поведению (undefined behaviour/unspecified behavior).

Стандарт Си++11 описывает работу операторов сдвига следующим образом:

"The shift operators << and >> group left-to-right.

shift-expression << additive-expression

shift-expression >> additive-expression

The operands shall be of integral or unscoped enumeration type and integral promotions are performed.

1. The type of the result is that of the promoted left operand. The behavior is undefined if the right operand is negative, or greater than or equal to the length in bits of the promoted left operand.

2. The value of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are zero-filled. If E1 has an unsigned type, the value of the result is E1 * 2^E2, reduced modulo one more than the maximum value representable in the result type. Otherwise, if E1 has a signed type and non-negative value, and E1*2^E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.

3. The value of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type or if E1 has a signed type and a non-negative value, the value of the result is the integral part of the quotient of E1/2^E2. If E1 has a signed type and a negative value, the resulting value is implementation-defined."

Приведем примеры, которые приводят к undefined или unspecified behavior:

int A = 1;
int B;
B = A << -3; // undefined behavior
B = A << 100; // undefined behavior
B = -1 << 5; // undefined behavior
B = -1 >> 5; // unspecified behavior

Конечно, это упрощенные примеры. В реальных программах ситуации сложнее. Рассмотрим практический пример:

SZ_RESULT
SafeReadDirectUInt64(ISzInStream *inStream, UInt64 *value)
{
  int i;
  *value = 0;
  for (i = 0; i < 8; i++)
  {
    Byte b;
    RINOK(SafeReadDirectByte(inStream, &b));
    *value |= ((UInt32)b << (8 * i));
  }
  return SZ_OK;
}

Функция пытается побайтно прочитать 64-битное значение. К сожалению, у неё это не получится, если число было больше 0x00000000FFFFFFFF. Обратите внимание на сдвиг "(UInt32)b << (8 * i)". Размер левого операнда составляет 32 бита. При этом сдвиг происходит от 0 до 56 бит. На практике это приведёт к тому, что старшая часть 64-битного значения останется заполнена нулями. Теоретически здесь вообще имеет место неопределенное поведение и результат непредсказуем.

Корректный код должен выглядеть так:

*value |= ((UInt64)b << (8 * i));

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

Рассмотрим более подробно ситуацию, когда левый операнд отрицателен. Как правило, создаётся впечатление, что такой код всегда работает правильно. Можно подумать, что хотя это неопределённое поведение (undefined behavior), фактически все компиляторы ведут себя одинаковым образом. Это не так. Правильнее говорить, что большинство компиляторов ведут себя одинаковым образом. Если вас заботит переносимость кода, вы не должны использовать сдвиги отрицательных значений.

Подкрепим свои слова примером. Неожиданный результат можно получить, используя компилятор GCC для микропроцессора MSP430. Здесь описывается подобная ситуация. Хотя программист заявляет, что это ошибка компилятора, фактически мы имеем тот самый случай, когда компилятор ведёт себя по-другому.

Тем не менее, мы понимаем желание программистов отключить предупреждение для случаев, когда левый операнд отрицателен. Для этого можно вписать где-то в текст программы специальный комментарий:

//-V610_LEFT_SIGN_OFF

Этот комментарий следует вписать в заголовочный файл, который включается во все другие файлы. Например, таким файлом может быть "stdafx.h". Если вписать этот комментарий в "*.cpp" файл, то он будет действовать только для этого файла.

Данная диагностика классифицируется как:

Взгляните на примеры ошибок, обнаруженных с помощью диагностики V610.

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
Ваше сообщение отправлено.

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


Если вы так и не получили ответ, пожалуйста, проверьте, отфильтровано ли письмо в одну из следующих стандартных папок:

  • Промоакции
  • Оповещения
  • Спам