# V641. Buffer size is not a multiple of element size.

Анализатор обнаружил потенциальную ошибку, связанную c приведением указателя на исходный буфер к указателю на другой тип, при этом размер исходного буфера некратен размеру одного элемента полученного типа. Рассмотрим два класса ошибок, допускаемых при приведениях типов.

Первый класс ошибок связан с выделением некорректного размера памяти для хранения элементов массива с помощью функций 'malloc', 'calloc', 'alloca', 'realloc' и т.д.

Как правило, такие ошибки возникают, если размер выделяемой памяти задается константой (константами в случае 'calloc'). Для правильного выделения памяти под 'N' элементов массива типа 'T' рекомендуется использовать оператор 'sizeof(T)'. В зависимости от функции, аллоцирующей память, конструкция может иметь следующий вид:

    int *p = (int*)malloc(N * sizeof(int));

или

    int *p = (int*)calloc(N, sizeof(int));

В результате неправильного выделения памяти в программе может возникнуть выход за границу массива.

Рассмотрим пример некорректного кода с использованием функции 'malloc':

    int *p = (int*)malloc(70);

В данном случае будет выделено 70 байт памяти. Обращение к элементу 'p[17]' приведет к неопределенному поведению, поскольку произойдет выход за границу (необходимо 72 байта для корректного чтения 18-го элемента). Корректный вариант кода будет следующим:

    p = (int*)malloc(72);

Также возможен случай, когда требуется выделить память для хранения 70 элементов. Корректным будет такой вариант кода:

    p = (int*)malloc(70 * sizeof(int));

Рассмотрим пример некорректного кода из реального проекта с использованием функции 'calloc':

    int data16len = MultiByteToWideChar(CP_UTF8,
                                        0,
                                        data,
                                        datalen,
                                        NULL,
                                        0);
    if (!data16)
    {
      data16 = (wchar_t*)calloc(data16len + 1, 1);
    }
    MultiByteToWideChar(CP_UTF8, 0, data, -1, data16, data16len);

В данном случае хотели создать буфер для сохранения широкой (wide) строки после конвертации из UTF-8 строки. Однако размер 'wchar_t' не равен 1 байту (Windows - 2 байта, Linux - 4 байта). Корректный вариант кода будет выглядеть следующим образом:

    data16 = (wchar_t*)calloc(data16len + 1, sizeof(wchar_t));

Примечание к функции 'calloc'. Несмотря на то, что прототип функции выглядит следующим образом:

    void* calloc(size_t num, size_t size );

некоторые программисты полагают, что размер выделяемой памяти равен num*size и часто меняют аргументы местами. Такой код может приводить к ошибкам. Цитата из документации: "Due to the alignment requirements, the number of allocated bytes is not necessarily equal to num*size.".

Второй класс ошибок связан с преобразованием указателя на объект типа 'A' к указателю на объект типа 'B'. Рассмотрим пример:

    struct A
    {
      int a, b;
      float c;
      unsigned char d;
    };

    struct B
    {
      int a, b;
      float c;
      unsigned short d;
    };

    ....
    A obj1;
    B *obj2 = (B*)&obj1; //V641
    std::cout << obj2->d;
    ....

Как видно из примера, две структуры отличаются друг от друга последним полем. Поле 'd' у приведённых структур имеет разный размер типа. При преобразовании указателя 'A*' к 'B*' можно получить неопределенное поведение при обращении к полю 'd'. Отметим, что преобразование указателя 'B*' к 'A*' возможно (хотя это плохой код), и неопределенного поведения не будет.

Анализатор не будет выдавать предупреждения на преобразование указателя 'A*' к 'B*', если два класса (структуры) находятся в иерархии наследования.

Выявляемые диагностикой ошибки классифицируются согласно ГОСТ Р 71207–2024 как критические и относятся к типу: Ошибки управления динамической памятью (выделения, освобождения, использования освобожденной памяти) [* см. примечание касательно языков C#, Java].

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

CWE-704 CERT-EXP36-C

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