Данное диагностическое правило основано на руководстве 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) { double diff = fabs(lhs - rhs); lhs = fabs(lhs); rhs = fabs(rhs); double largest = (rhs > lhs) ? rhs : lhs; return diff <= largest * eps; }
Случай N3. Символьные массивы. Рассмотрим пример:
bool equals(const char *lhs, const char *rhs, size_t length) { return memcmp(lhs, rhs, length) == 0; }
При применении memcmp
при сравнении символьных массивов терминальный ноль может быть интерпретирован как данные, а не как сигнал об остановке алгоритма. Из-за этого может произойти либо неверное сравнение, либо выход за границу массива, что ведёт к неопределённому поведению.
Корректный способ сравнения символьных массивов:
bool equals(const char *lhs, const char *rhs, size_t length) { return strncmp(lhs, rhs, length) == 0; }
Данная диагностика классифицируется как:
|