# V1103. The values of padding bytes are unspecified. Comparing objects with padding using 'memcmp' may lead to unexpected result.

Анализатор обнаружил фрагмент кода, в котором сравниваются объекты структур, содержащие байты выравнивания.

Рассмотрим синтетический пример:

    struct Foo
    {
      unsigned char a;
      int i;
    };

    void bar()
    {
      Foo obj1 { 2, 1 };
      Foo obj2 { 2, 1 };

      auto result = std::memcmp(&obj1, &obj2, sizeof(Foo)); // <=
    }

Чтобы понять суть проблемы, надо рассмотреть расположение объектов класса 'C' в памяти:

    [offset 0] unsigned char
    [offset 1] padding byte
    [offset 2] padding byte
    [offset 3] padding byte
    [offset 4] int, first byte
    [offset 5] int, second byte
    [offset 6] int, third byte
    [offset 7] int, fourth byte

Для того, чтобы корректно и эффективно работать с объектами в памяти, компилятор применяет выравнивание данных. На типовых моделях данных выравнивание типа 'unsinged char' равно 1, а типа 'int' – 4. Это означает, адрес поля 'Foo::i' должен быть кратен 4. Чтобы сделать это, компилятор вставит 3 байта выравнивания после поля 'Foo::a'.

Стандарты C и C++ не уточняют, будут ли занулены байты выравнивания при инициализации объекта. Следовательно, при попытке побайтового сравнения двух объектов с одинаковыми значениями полей при помощи функции 'memcmp' результат может не всегда равняться 0.

Исправить проблему можно несколькими способами.

Способ N1 (предпочтительный). Написать компаратор и сравнивать объекты при помощи него.

Для языка C:

    struct Foo
    {
      unsigned char a;
      int i;
    };

    bool Foo_eq(const Foo *lhs, const Foo *rhs)
    {
      return lhs->a == rhs->a && lhs->i == rhs->i;
    }

Для языка C++:

    struct Foo
    {
      unsigned char a;
      int i;
    };

    bool operator==(const Foo &lhs, const Foo &rhs) noexcept
    {
      return lhs.a == rhs.a && lhs.i == rhs.i;
    }

    bool operator!=(const Foo &lhs, const Foo &rhs) noexcept
    {
      return !(lhs == rhs);
    }

Начиная с C++20, код можно упростить, указав компилятору самостоятельно сгенерировать компаратор:

    struct Foo
    {
      unsigned char a;
      int i;

      auto operator==(const Foo &) const noexcept = default;
    };

Способ N2. Предварительно занулять объекты.

    struct Foo
    {
      unsigned char a;
      int i;
    };

    bool Foo_eq(const Foo *lhs, const Foo *rhs)
    {
      return lhs->a == rhs->a && lhs->i == rhs->i;
    }

    void bar()
    {
      Foo obj1;
      memset(&obj1, 0, sizeof(Foo));
      Foo obj2;
      memset(&obj2, 0, sizeof(Foo));

      // initialization part

      auto result = Foo_eq(&obj1, &obj2);
    }

Однако этот способ имеет недостатки:

- Вызов 'memset' вносит накладные расходы на зануление всего участка памяти.
- Перед вызовом 'memcmp' нужно также гарантировать, что память под объект была занулена. В проекте со сложным потоком управления об этом можно легко забыть.

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

CWE-188 CERT-EXP42-C

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