>
>
>
V2629. MISRA. Pointer arguments to the …


V2629. MISRA. Pointer arguments to the 'memcmp' function should point to an appropriate type.

Данное диагностическое правило основано на руководстве MISRA (Motor Industry Software Reliability Association) по разработке программного обеспечения.

Это правило актуально только для C.

Использование функции memcmp из стандартной библиотеки может приводить к неожиданным результатам при использовании с определёнными типами данных.

Функция производит побайтовое сравнение первых n байт двух объектов, переданных через указатели. Однако есть случаи, когда не следует использовать memcmp для побайтового сравнения.

Случай N1. Структуры или объединения. Из-за байтов выравнивания при логически равных объектах можно получить различный результат.

Пример:

struct S 
{
  int  a;
  char b;
};

bool equals(struct S *s1, struct S *s2)
{
  return memcmp(s1, s2, sizeof(struct S)) == 0;
}

Для дальнейшего понимания примем, что размер и выравнивание типа int равно 4, а для char1. Вследствие того, что процессор должен эффективно работать с данными в памяти, структура S будет располагаться в памяти следующим образом:

class S size(8):
     +---
 0   | a
 4   | b
     | <alignment member> (size=3)
     +---

Первое поле расположено по смещению 0x0 и занимает 4 байта. Следующее поле расположено по смещению 0x4 и занимает 1 байт. Помимо этого, начиная со смещения 0x5 в объекты класса добавляются 3 байта для того, чтобы выровнить объекты класса по максимальному выравниванию в 4 байта.

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

Корректным способом сравнения двух объектов будет попарное сравнение всех полей класса:

struct S 
{
  int  a;
  char b;
};

bool equals(struct S *s1, struct S *s2)
{
  return s1->a == s2->a && s1->b == s2->b;
}

Случай N2. Вещественные числа. Рассмотрим пример:

bool equals(double *lhs, double *rhs, size_t length)
{
  return memcmp(lhs, rhs, length * sizeof(double)) == 0;
}

Функция производит сравнение двух массивов вещественных чисел размера length. Из-за особенностей формата представления вещественных чисел одинаковые значения могут иметь различные байтовые представления. Из-за этого memcmp вернёт не тот результат, что ожидает разработчик.

Более корректный способ сравнения выглядит следующим образом:

bool equals_d(double lhs, double rhs, double eps = DBL_EPSILON);

bool equals(double *lhs, double *rhs, size_t length)
{
  for (size_t i = 0; i < length; ++i)
  {
    if (!equals_d(lhs[i], rhs[i])) return false;
  }

  return true;
}

Функция сравнения двух вещественных чисел подбирается исходя из условий сравнения. Например, она может быть такой:

bool equals_d(double lhs, double rhs, double eps)
{
  float diff = fabs(lhs - rhs);
  lhs = fabs(lhs);
  rhs = fabs(rhs);

  double largest = (B > A) ? B : A;

  return diff <= largest * maxRelDiff;
}

Случай N3. Символьные массивы. Рассмотрим пример:

bool equals(const char *lhs, const char *rhs, size_t length)
{
  return memcmp(str1, str2, length) == 0;
}

При применении memcmp при сравнении символьных массивов терминальный ноль может быть интерпретирован как данные, а не как сигнал об остановке алгоритма. Из-за этого может произойти либо неверное сравнение, либо выход за границу массива, что ведёт к неопределённому поведению.

Корректный способ сравнения символьных массивов:

bool equals(const char *lhs, const char *rhs, size_t length)
{
  return strncmp(lhs, rhs, length) == 0;
}