Мы используем куки, чтобы пользоваться сайтом было удобно.
Хорошо
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 и нажмите на письме кнопку "Не спам".
Так Вы не пропустите ответы от нашей команды.

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

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

24 Янв 2012

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

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/image1.png

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

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

ptrdiff_t mask = ptrdiff_t(1) << bitNum;

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

11_Pattern_03_Shift_operations_ru/image3.png

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

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

11_Pattern_03_Shift_operations_ru/image4.png

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

Описанный вид ошибок можно считать опасным не только с точки зрения корректности работы программы, но и с точки зрения безопасности. Потенциально манипулируя с входными данными подобных некорректных функций, можно получить недопустимо высокие права, если, например, происходит обработка масок прав доступа, заданных отдельными битами. Вопросы, связанные с использованием ошибок в 64-битном коде для взлома и компрометации приложений, затронуты нами в статье "Безопасность 64-битного кода".

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

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

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

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

#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

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

Член структуры obj.a преобразуется из битового поля типа unsigned short в int. Выражение "obj.a << 17" имеет тип int, но оно преобразуется в ptrdiff_t и затем в size_t перед тем, как будет присвоено переменной addr. В результате мы получим значение 0xffffffff80000000, вместо ожидаемого 0x0000000080000000.

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

...
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, когда он обнаружит неявное расширение 32-битного типа до memsize типа. Анализатор предупредит об опасной конструкции диагностическим сообщением V101. При этом сама по себе операция сдвига подозрения не вызывает. Но анализатор обнаруживает неявное расширение типа int до типа memsize при присваивании и информирует об этом программиста, который может обнаружить ошибку в коде. Соответственно, если расширения нет, то код считается анализатором безопасным. Пример: "int mask = 1 << bitNum;".

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

Правообладателем курса "Уроки разработки 64-битных приложений на языке Си/Си++" является ООО "Системы программной верификации". Компания занимается разработкой программного обеспечения в области анализа исходного кода программ. Сайт компании: http://www.viva64.com.