# V101. Implicit assignment type conversion to memsize type.

Анализатор обнаружил потенциально возможную ошибку, связанную с неявным приведением типа при выполнении оператора присваивания "=". Ошибка может заключаться в некорректном вычислении значения выражения, стоящего справа от оператора присваивания "=".

Пример кода, вызывающего предупреждение:

    size_t a;
    unsigned b;
    ...
    a = b; // V101

Сама по себе операция приведения 32-битного типа к memsize-типу безопасна, поскольку не происходит потери данных. Например, в переменной типа size_t всегда можно сохранить значение переменной типа unsigned. Но наличие такого приведения типа может указывать на скрытую ошибку, допущенную ранее.

Первой причиной возникновения ошибки на 64-битной системе может служить изменение процесса вычисления выражений. Рассмотрим пример:

    unsigned a = 10;
    int b = -11;
    ptrdiff_t c = a + b; //V101
    cout << c << endl;

Данный код на 32-битной системе распечатает значение -1, а на 64-битной системе 4294967295. Данное поведение полностью согласуется с правилами приведения типов в языке Си++, но скорее всего приведет к ошибке в реальном коде.

Поясним приведенный пример. Согласно правилам языка Си++ выражение a+b имеет тип unsigned и содержит значение 0xFFFFFFFFu. На 32-битной системе тип ptrdiff_t представляет собой знаковый 32-битный тип. После присвоения 32-битной знаковой переменной значения 0xFFFFFFFFu она будет содержать значение -1. На 64-битной системе тип ptrdiff_t представляет собой знаковый 64-битный тип. Это означает, что число 0xFFFFFFFFu будет представлено как оно есть. То есть значение переменной после присваивания будет равняться 4294967295.

Ошибку можно исправить, исключив смешанное использования memsize и не memsize-типов в одном выражении. Пример исправления кода:

    size_t a = 10;
    ptrdiff_t b = -11;
    ptrdiff_t c = a + b;
    cout << c << endl;

Еще более корректным способом исправления будет отказ от смешенного использования знаковых и беззнаковых типов данных.

Второй причиной ошибки может стать переполнение, возникающее в 32-битных типах данных. В этом случае ошибка может содержаться раньше самого оператора присваивания, но обнаружить ее можно только косвенно. Такие ошибки часто встречаются в коде, выделяющим большие объемы памяти. Рассмотрим пример:

    unsigned Width  = 1800;
    unsigned Height = 1800;
    unsigned Depth  = 1800;
    // Real error is here
    unsigned CellCount = Width * Height * Depth;
    // Here we get a diagnostic message V101
    size_t ArraySize = CellCount * sizeof(char);
    cout << ArraySize << endl;
    void *Array = malloc(ArraySize);

Предположим, что на 64-битной системе мы решили обрабатывать массивы данных более 4 гигабайт. В этом случае показанный код приведет к выделению ошибочного объема памяти. Программист планирует выделить 5832000000 байт оперативной памяти, а вместо этого получит в свое распоряжение только 1537032704. Это происходит из-за переполнения при вычислении выражения Width * Height * Depth. К сожалению мы не можем диагностировать ошибку в строке с этим выражением, но мы можем косвенно указать на наличие ошибки, обнаружив приведение типа в строке:

    size_t ArraySize = CellCount * sizeof(char); //V101

Исправление ошибки заключается в использовании типов, позволяющих хранить необходимый диапозон значений. Заметьте, что исправлние следующего вида не является корректным:

    size_t CellCount = Width * Height * Depth;

Здесь мы по-прежнему имеем переполнение. Приведем два примера корректного исправления кода:

// 1)

    unsigned Width  = 1800;
    unsigned Height = 1800;
    unsigned Depth  = 1800;
    size_t CellCount =
      static_cast<size_t>(Width) *
      static_cast<size_t>(Height) *
      static_cast<size_t>(Depth);

// 2)

    size_t Width  = 1800;
    size_t Height = 1800;
    size_t Depth  = 1800;
    size_t CellCount = Width * Height * Depth;

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

Пусть приложение использует большой одномерный массив, и функция CalcIndex позволяет адресоваться к этому массиву как к двумерному массиву.

    extern unsigned ArrayWidth;
    unsigned CalcIndex(unsigned x, unsigned y) {
      return x + y * ArrayWidth;
    }
       ...
    const size_t index = CalcIndex(x, y); //V101

Анализатор сообщит о проблеме в строке: const size_t index = CalcIndex(x, y). Но ошибка заключается в неправильной реализации функции CalcIndex. Если взять функцию CalcIndex отдельно, то она абсолютно корректна. Типом выходного и входных значений является unsigned. Вычисления также происходят с участием только типов unsigned. Никаких явных или неявных приведений типов нет, и анализатор не имеет возможности распознать логическую проблему, связанную с функцией CalcIndex. Ошибка заключается в том, что неверно выбран результат, возвращаемый функцией, а возможно и входных значений. Результат функции должен иметь тип memsize.

К счастью анализатор смог обнаружить неявное приведение результата функции CalcIndex к типу size_t. Это позволяет вам проанализировать ситуацию и внести в программу необходимые изменения. Исправление ошибки, например, может выглядеть следующим образом:

    extern size_t ArrayWidth;
    size_t CalcIndex(size_t x, size_t y) {
      return x + y * ArrayWidth;
    }
       ...
    const size_t index = CalcIndex(x, y);

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

    extern unsigned ArrayWidth;
    unsigned CalcIndex(unsigned x, unsigned y) {
      return x + y * ArrayWidth;
    }
    ...
    const size_t index = static_cast<size_t>(CalcIndex(x, y));

В ряде случаев и сам анализатор может понять, что переполнение при вычислении невозможно и предупреждение не будет распечатано.

Рассмотрим последний пример, связанный с некорректными операциями сдвига.

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

Выражение " mask = 1 << bitNum " является опасным, так как данный код не может выставить в единицы старшие разряды 64-битной переменной mask. Если попробовать использовать функцию SetBitN для выставления, например, 33-го бита, то произойдет переполнение при выполнении операции сдвига и желаемый результат не будет достигнут.

Дополнительные материалы по данной теме:

- 64-битные уроки. Урок 11. Паттерн 3. Операции сдвига.
- 64-битные уроки. Урок 17. Паттерн 9. Смешанная арифметика.