V221. Suspicious sequence of types castings: pointer -> memsize -> 32-bit integer.
Предупреждение информирует о наличии странной последовательности приведений типа. Указатель явно приводится к memsize-типу. А затем вновь явно или неявно приводится к 32-битному целочисленному типу. Такая последовательность приведений приводит к потере значений старших бит. Как правило, это свидетельствует о наличии серьезной ошибки.
Рассмотрим пример:
int *p = Foo();
unsigned a, b;
a = size_t(p);
b = unsigned(size_t(p));
В обоих случаях указатель преобразовывается в тип 'unsigned'. При этом старшая часть указателя будет потеряна. Если затем переменные 'a' или 'b' вновь превратить в указатель, эти указатели могут оказаться некорректными.
Различие между переменными 'a' и 'b' только в том, что второй случай тяжелей обнаружить. В первом случае, компилятор предупредит о потери значащих бит. Во втором случае он будет молчать, так как используется явное приведение типа.
Исправление ошибки заключается в том, чтобы хранить указатели только memsize-типах, например, в переменных типа size_t:
int *p = Foo();
size_t a, b;
a = size_t(p);
b = size_t(p);
Иногда некоторое недопонимание вызывает предупреждение анализатора на код следующего вида:
BOOL Foo(void *ptr)
{
return (INT_PTR)ptr;
}
Тип BOOL это не что иное как 32-битный тип 'int'. Поэтому возникает цепочка преобразований вида:
pointer -> INT_PTR -> int.
Может показаться, что здесь ошибки нет. Ведь нам только важно, что указатель равен или не раен нулю. Но на самом деле ошибка есть. Не следует путать как ведёт себя тип BOOL и bool.
Пусть у нас есть 64-битная переменная, значение которой равно 0x000012300000000. Тогда при приведении к bool и BOOL мы получим разные результаты:
int64_t v = 0x000012300000000ll;
bool b = (bool)(v); // true
BOOL B = (BOOL)(v); // FALSE
В случае 'BOOL' просто будут отброшены старшие биты. И ненулевое значение превратится в 0 (FALSE).
Аналогичная ситуация с указателем. При явном приведении указателя к BOOL отпросятся старшие биты и ненулевой указатель превратится в целочисленный 0 (FALSE). Вероятность такого события мала, но есть. Поэтому такой код неверен.
Для того чтобы код стал корректным, можно поступить двумя способами. Первый вариант - использовать тип 'bool':
bool Foo(void *ptr)
{
return (INT_PTR)ptr;
}
Хотя конечно лучше и проще написать так:
bool Foo(void *ptr)
{
return ptr != nullptr;
}
Показанный выше вариант не всегда возможен. Например, в языке Си нет типа 'bool'. Второй вариант исправления ошибки:
BOOL Foo(void *ptr)
{
return ptr != NULL;
}
Следует учитывать, что анализатор не выдаёт предупреждение, если конвертации подвергается такие типы данных, как HANDLE, HWND, HCURSOR и так далее. Хотя по сути это указатели (void *), их значения всегда вмещаются в младшие 32-бита. Это сделано специально, чтобы эти дескрипторы (handles) можно было передавать между 32-битными и 64-битными процессами. Подробнее: Как корректно привести указатель к int в 64-битной программе?