﻿# Урок 11\. Паттерн 3\. Операции сдвига

Легко сделать ошибку в коде, работающем с отдельными битами\. Рассматриваемый паттерн 64\-битных ошибок связан с операциями сдвига\. Пример кода:

```cpp
ptrdiff_t SetBitN(ptrdiff_t value, unsigned bitNum) {
  ptrdiff_t mask = 1 << bitNum;
  return value | mask;
}
```

Приведенный код работоспособен на 32\-битной архитектуре и позволяет выставлять бит с номерами от 0 до 31 в единицу\. После переноса программы на 64\-битную платформу возникнет необходимость выставлять биты от 0 до 63\. Но данный код никогда не выставит биты с номерами 32\-63\. Обратите внимание, что числовой литерал "1" имеет тип int, и при сдвиге на 32 позиции произойдет переполнение, как показано на рисунке 1\. Получим мы в результате 0 \(рисунок 1\-B\) или 1 \(рисунок 1\-C\) \- зависит от реализации компилятора\.

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

_Рисунок 1 \- a\) корректная установка 31\-ого бита в 32\-битном коде; b,c\) \- Ошибка установки 32\-ого бита на 64\-битной системе \(два варианта поведения\)_

Для исправления кода необходимо сделать константу "1" того же типа, что и переменная mask:

```cpp
ptrdiff_t mask = ptrdiff_t(1) << bitNum;
```

Заметим также, что неисправленный код приведет еще к одной интересной ошибке\. При выставлении 31 бита на 64\-битной системе результатом работы функции будет значение 0xffffffff80000000 \(см\. рисунок 2\)\. Результатом выражения 1 << 31 является отрицательное число \-2147483648\. Это число представляется в 64\-битной целой переменной как 0xffffffff80000000\.

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

_Рисунок 2 \- Ошибка установки 31\-ого бита на 64\-битной системе\._

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

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

_Таблица 1 \- Выражения со сдвигами и результаты в 64\-битной системе \(использовался компилятор Visual C\+\+ 2005\)_

Описанный вид ошибок можно считать опасным не только с точки зрения корректности работы программы, но и с точки зрения безопасности\. Потенциально манипулируя с входными данными подобных некорректных функций, можно получить недопустимо высокие права, если, например, происходит обработка масок прав доступа, заданных отдельными битами\. Вопросы, связанные с использованием ошибок в 64\-битном коде для взлома и компрометации приложений, затронуты нами в статье "[Безопасность 64\-битного кода](https://pvs-studio.ru/ru/blog/posts/cpp/a0046/)"\.

Рассмотрим теперь более тонкий пример:

```cpp
struct BitFieldStruct {
  unsigned short a:15;
  unsigned short b:13;
};
BitFieldStruct obj;
obj.a = 0x4000;
size_t addr = obj.a << 17; //Sign Extension
printf("addr 0x%Ix\n", addr);
//Output on 32-bit system: 0x80000000
//Output on 64-bit system: 0xffffffff80000000
```

В 32\-битной среде порядок вычисления выражения будет выглядеть, как показано на рисунке 3\.

![11_Pattern_03_Shift_operations_ru/image6.png](https://import.viva64.com/docx/blog/11_Pattern_03_Shift_operations_ru/image6.png)

_Рисунок 3 \- Вычисление выражения в 32\-битном коде_

Обратим внимание, что при вычислении выражения "obj\.a << 17" происходит знаковое расширение типа unsigned short до типа int\. Более наглядно это может продемонстрировать следующий код:

```cpp
#include <stdio.h>
template <typename T> void PrintType(T)
{
  printf("type is %s %d-bit\n",
          (T)-1 < 0 ? "signed" : "unsigned", sizeof(T)*8);
}
struct BitFieldStruct {
  unsigned short a:15;
  unsigned short b:13;
};
int main(void)
{
  BitFieldStruct bf;
  PrintType( bf.a );
  PrintType( bf.a << 2);
  return 0;
}
Result:
type is unsigned 16-bit
type is signed 32-bit
```

Теперь посмотрим, к чему приводит наличие знакового расширения в 64\-битном коде\. Последовательность вычисления выражения показана на рисунке 4\.

![11_Pattern_03_Shift_operations_ru/image7.png](https://import.viva64.com/docx/blog/11_Pattern_03_Shift_operations_ru/image7.png)

_Рисунок 4 \- Вычисление выражения в 64\-битном коде\._

Член структуры obj\.a преобразуется из битового поля типа unsigned short в int\. Выражение "obj\.a << 17" имеет тип int, но оно преобразуется в [ptrdiff\_t](https://pvs-studio.ru/ru/blog/terms/0041/) и затем в [size\_t](https://pvs-studio.ru/ru/blog/terms/0044/) перед тем, как будет присвоено переменной addr\. В результате мы получим значение 0xffffffff80000000, вместо ожидаемого 0x0000000080000000\.

Будьте внимательны при работе с битовыми полями\. Для предотвращения описанной ситуации в нашем примере достаточно явно привести obj\.a к типу size\_t\.

```cpp
...
size_t addr = size_t(obj.a) << 17;
printf("addr 0x%Ix\n", addr);
//Output on 32-bit system: 0x80000000
//Output on 64-bit system: 0x80000000
```

## Диагностика

Потенциально опасные сдвиги будут выявлены статическим анализатором [PVS\-Studio](https://pvs-studio.ru/ru/pvs-studio/), когда он обнаружит неявное расширение 32\-битного типа до [memsize](https://pvs-studio.ru/ru/blog/terms/0030/) типа\. Анализатор предупредит об опасной конструкции диагностическим сообщением [V101](https://pvs-studio.ru/ru/docs/warnings/v101/)\. При этом сама по себе операция сдвига подозрения не вызывает\. Но анализатор обнаруживает неявное расширение типа int до типа memsize при присваивании и информирует об этом программиста, который может обнаружить ошибку в коде\. Соответственно, если расширения нет, то код считается анализатором безопасным\. Пример: "int mask \= 1 << bitNum;"\.

_Авторы курса: Андрей Карпов \([karpov@viva64\.com](mailto:karpov@viva64.com)\), Евгений Рыжков \([evg@viva64\.com](mailto:evg@viva64.com)\)\._

_Правообладателем курса "Уроки разработки 64\-битных приложений на языке Си/Си\+\+" является ООО "Системы программной верификации"\. Компания занимается разработкой программного обеспечения в области анализа исходного кода программ\. [Сайт компании](https://pvs-studio.ru/ru/pvs-studio/)\._