>
>
>
V1103. The values of padding bytes are …


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' нужно также гарантировать, что память под объект была занулена. В проекте со сложным потоком управления об этом можно легко забыть.

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