Для получения триального ключа
заполните форму ниже
Team License (базовая версия)
Enterprise License (расширенная версия)
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

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

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

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

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

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

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


Если вы так и не получили ответ, пожалуйста, проверьте папку
Spam/Junk и нажмите на письме кнопку "Не спам".
Так Вы не пропустите ответы от нашей команды.

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

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".

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

Дополнительные возможности

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

Возле прототипа функции (или возле её реализации, или в общем заголовочном файле) пишется комментарий специального вида. Пример использования:

//+V576, function:Mylog, format_arg:1, ellipsis_arg:2
Mylog("%f", time(NULL)); // warning V576

Формат:

  • Ключи function, class и namespace задают имя функции, имя класса (если нужно анализировать вызов только этого метода класса) и имя пространства имён (если требуется анализировать функцию или метод класса только этого пространства имён).
  • Ключ format_arg задаёт номер аргумента функции, в котором будет находиться форматная строка. Это обязательный аргумент. Номера также считаются с единицы и также не должны превышать число 14.
  • Ключ ellipsis_arg задаёт номер аргумента функции с эллипсисом (то есть многоточием). К этому номеру предъявляются те же ограничения, что и к номеру форматной строки. Более того, номер аргумента с эллипсисом должен быть больше номера аргумента с форматной строкой (всё-таки эллипсис – исключительно последний аргумент). Это также обязательный аргумент.

Напоследок, дадим наиболее полный пример использования:

// Предупреждать, когда в методе C класса B
// из пространства имён A аргументы, начиная с
// третьего, не совпадают с типом, заданным в
// форматной строке из второго аргумента. 
//+V576,namespace:A,class:B,function:C,format_arg:2,ellipsis_arg:3

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

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

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

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