V2646. MISRA. All arguments of any multi-argument type-generic macros from <tgmath.h> should have the same type.

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

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

Все аргументы, передаваемые в макросы из заголовочного файла <tgmath.h>, должны иметь одинаковый тип. Целочисленные аргументы после преобразования (integer promotion) также должны быть одного типа.

Правило применимо к следующим макросам: atan2, copysign, fdim, fma, fmax, fmin, fmod, frexp, hypot, ldexp, nextafter, nexttoward, pow, remainder, remquo, scalbn, scalbln.

Примечание. Последний аргумент макросов frexp и remquo используется для записи результата, его тип может отличаться от остальных.

На основе типов аргументов выбирается вызываемая функция и, соответственно, тип возвращаемого значения. В C нет механизма перегрузки функций, однако выбор подходящей альтернативы при вызове соответствующего макроса всё же может быть реализован в конкретном компиляторе. Например, в MSVC CL для этого используется механизм generic selection. Если аргументы имеют разные типы, может быть выбрана неправильная функция, что, в свою очередь, может привести к непредсказуемому или неточному результату.

Рассмотрим пример:

#include <tgmath.h>

void first()
{
  float op1 = 15.61;
  float op2 = 31.4;
  float res = pow(op1, op2); //2.970643e+37
}

void second()
{
  double op1 = 15.61;
  float op2 = 31.4;
  float res = pow(op1, op2); //2.970645e+37
}

В приведённом коде аргументы макроса имеют типы разных размеров. В результате будут вызваны разные функции: float powf(float, float) в первом случае и double pow(double, double) — во втором. Логика выбора конкретной функции зависит от компилятора и не всегда прозрачна. К тому же, результат вычислений во втором примере будет неявно преобразован из типа double к типу float. Подобные преобразования в некоторых случаях могут привести к потере точности или немного разным результатам вычислений.

Избавиться от ошибки можно явно приведя аргументы к одинаковому типу. В таком случае после раскрытия макроса будет вызвана соответствующая функция и возвращаемое значение гарантированно будет иметь тип float, а результаты не будут отличаться.

Исправленный пример:

#include <tgmath.h>
 
void second()
{
  double op1 = 15.61;
  float op2 = 31.4;
  float res = pow((float)op1, op2); //2.970643e+37
}

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

  • MISRA-C-2023-21.23