Мы используем куки, чтобы пользоваться сайтом было удобно.
Хорошо
to the top
close form

Заполните форму в два простых шага ниже:

Ваши контактные данные:

Шаг 1
Поздравляем! У вас есть промокод!

Тип желаемой лицензии:

Шаг 2
Team license
Enterprise license
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности
close form
Запросите информацию о ценах
Новая лицензия
Продление лицензии
--Выберите валюту--
USD
EUR
RUB
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Бесплатная лицензия PVS‑Studio для специалистов Microsoft MVP
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Для получения лицензии для вашего открытого
проекта заполните, пожалуйста, эту форму
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Мне интересно попробовать плагин на:
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
check circle
Ваше сообщение отправлено.

Мы ответим вам на


Если вы так и не получили ответ, пожалуйста, проверьте, отфильтровано ли письмо в одну из следующих стандартных папок:

  • Промоакции
  • Оповещения
  • Спам

Вебинар: Использование статических анализаторов кода при разработке безопасного ПО - 19.12

>
>
>
V576. Incorrect format. Consider checki…
menu mobile close menu
Проверка проектов
Сообщения PVS-Studio
Диагностики общего назначения (General Analysis, C++)
Диагностики общего назначения (General Analysis, C#)
Диагностики общего назначения (General Analysis, Java)
Микрооптимизации (C++)
Диагностика 64-битных ошибок (Viva64, C++)
Реализовано по запросам пользователей (C++)
Cтандарт MISRA
Стандарт AUTOSAR
Стандарт OWASP (C++)
Стандарт OWASP (C#)
Проблемы при работе анализатора кода
Дополнительная информация
toggle menu Оглавление

V576. Incorrect format. Consider checking the Nth actual argument of the 'Foo' function.

09 Дек 2014

Анализатор обнаружил потенциальную ошибку при использовании функций форматного вывода ('printf', 'sprintf', 'wprintf' и так далее). Строка форматирования не соответствует передаваемым в функцию фактическим аргументам.

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

int A = 10;
double B = 20.0;
printf("%i %i\n", A, B);

Согласно строке форматирования, функция 'printf' ожидает два фактических аргумента типа 'int'. Однако второй аргумент имеет значение типа 'double'. Подобное несоответствие приводит к неопределённому поведению программы. Например, к распечатке бессмысленных значений.

Корректный вариант:

int A = 10;
double B = 20.0;
printf("%i %f\n", A, B);

Ошибочных вариантов использования функции 'printf' можно привести огромное количество. Рассмотрим только несколько типовых примеров, которые чаще всего можно встретить в программах.

Распечатка адреса

Очень часто значение указателя пытаются распечатать, используя следующий код:

int *ptr = new int[100];
printf("0x%0.8X\n", ptr);

Этот код ошибочен, поскольку будет работать только в тех системах, где размер указателя совпадает с размером типа 'int'. А, например, в Win64 этот код уже распечатает только младшую часть указателя 'ptr'. Корректный вариант кода:

int *ptr = new int[100];
printf("0x%p\n", ptr);

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

Неиспользуемые аргументы

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

int nDOW;
#define KEY_ENABLED "Enabled"
...
wsprintf(cDowKey, L"EnableDOW%d", nDOW, KEY_ENABLED);

Очевидно, что параметр 'KEY_ENABLED' здесь лишний, или код должен был выглядеть следующим образом:

wsprintf(cDowKey, L"EnableDOW%d%s", nDOW, KEY_ENABLED);

Недостаточное количество аргументов

Намного более опасной ситуацией является, когда в функцию передаётся меньше аргументов, чем необходимо. Это может легко привести к ошибке доступа к памяти, переполнению буфера или распечатке мусора. Рассмотрим пример функции выделения памяти, взятой из одной реальной программы:

char* salloc(register int nbytes)
{
    register char* p;
    p = (char*) malloc((unsigned)nbytes);
    if (p == (char *)NULL)
    {
        fprintf(stderr, "%s: out of memory\n");
        exit(1);
    }
    return (p);
}

Если функция 'malloc' вернёт значение 'NULL', то программа не сможет корректно сообщить о нехватке памяти и завершить свою работу. Она аварийно завершится или распечатает непонятный текст. В любом случае, подобное поведение усложнит анализ причины неработоспособности программы.

Путаница с signed/unsigned

Очень часто программисты используют спецификатор печати знаковых значений (например '%i') для печати переменных типа 'unsigned'. И наоборот. Эта ошибка, как правило, не критична и так сильно распространена, что в анализаторе она имеет низкий приоритет. Во многих случаях подобный код успешно работает и даёт сбой только при больших или отрицательных значениях. Рассмотрим код, который хотя не корректен, но успешно работает:

int A = 10;
printf("A = %u\n", A);
for (unsigned i = 0; i != 5; ++i)
  printf("i = %d\n", i);

Хотя здесь имеется несоответствие, это код на практике печатает корректные значения. Конечно, всё равно так лучше не делать и написать корректно:

int A = 10;
printf("A = %d\n", A);
for (unsigned i = 0; i != 5; ++i)
  printf("i = %u\n", i);

Ошибка проявит себя в том случае, если в программе имеются большие или отрицательные значения. Пример:

int A = -1;
printf("A = %u", A);

Вместо строки "A = -1" программа распечатает "A = 4294967295". Корректный вариант:

printf("A = %i", A);

Широкие строки (Wide character string)

У Visual Studio есть неприятная особенность, что он нестандартно интерпретирует формат строки для печати широких символов. В результате анализатор помогает диагностировать ошибку, например, в таком коде:

const wchar_t *p = L"abcdef";
wprintf(L"%S", p);

В Visual C++ считается, что '%S' предназначен для печати строки типа 'const char *'. Поэтому с точки зрения Visual C++ правильным является код:

wprintf(L"%s", p);

Начиная с Visual Studio 2015 предлагается решение этой проблемы, чтобы писать переносимый код. Для совместимости с ISO C (C99) следует указать препроцессору макрос _CRT_STDIO_ISO_WIDE_SPECIFIERS.

В этом случае, код:

const wchar_t *p = L"abcdef";
wprintf(L"%S", p);

является правильным.

Анализатор знает про '_CRT_STDIO_ISO_WIDE_SPECIFIERS' и учитывает его при анализе.

Кстати, если вы включили режим совместимости с ISO C (объявлен макрос '_CRT_STDIO_ISO_WIDE_SPECIFIERS'), вы можете в отдельных местах вернуть старое приведение, используя спецификатор формата '%Ts'.

Вся эта история с широкими символами достаточно запутанная и выходит за пределы документации. Чтобы лучше разобраться в вопросе, предлагаем ознакомиться со следующими ссылками:

Дополнительная настройка диагностики

Чтобы самостоятельно указать имена своих собственных функций, для которых следует выполнять проверку формата, можно использовать пользовательские аннотации. Подробнее об этом можно почитать здесь.

Дополнительные ресурсы:

Выявляемые диагностикой ошибки классифицируются согласно ГОСТ Р 71207–2024 как критические и относятся к типу: Ошибки использования форматной строки.

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

Взгляните на примеры ошибок, обнаруженных с помощью диагностики V576.