Данное диагностическое правило основано на руководстве 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
, а для char
– 1
. Вследствие того, что процессор должен эффективно работать с данными в памяти, структура 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;
}