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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

```cpp
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'\. И наоборот\. Эта ошибка, как правило, не критична и так сильно распространена, что в анализаторе она имеет низкий приоритет\. Во многих случаях подобный код успешно работает и даёт сбой только при больших или отрицательных значениях\. Рассмотрим код, который хотя не корректен, но успешно работает:

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

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

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

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

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

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

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

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

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

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

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

```cpp
wprintf(L"%s", p);
```

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

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

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

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

Анализатор знает про '\_CRT\_STDIO\_ISO\_WIDE\_SPECIFIERS' и учитывает его при анализе\.

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

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

* [Bug 1121290 \- distinguish specifier s and ls in the printf family of functions](https://bugzilla.mozilla.org/show_bug.cgi?id=1121290)
* [Visual Studio swprintf is making all my %s formatters want wchar\_t \* instead of char \*](https://stackoverflow.com/questions/10000723/visual-studio-swprintf-is-making-all-my-s-formatters-want-wchar-t-instead-of)
* Update\. В 2019 году появилась статья, которая рассказывает, почему получилась путаница: [The sad history of Unicode printf\-style format specifiers in Visual C\+\+](https://devblogs.microsoft.com/oldnewthing/20190830-00/?p=102823)\.

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

Чтобы самостоятельно указать имена своих собственных функций, для которых следует выполнять проверку формата, можно использовать пользовательские аннотации\. Подробнее об этом можно почитать [здесь](https://pvs-studio.ru/ru/docs/manual/0040/)\.

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

1. Wikipedia\. [Printf\.](https://ru.wikipedia.org/wiki/Printf) 
1. MSDN\. [Format Specification Fields: printf and wprintf Functions\.](https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/56e442dc(v=vs.100)?redirectedfrom=MSDN)