﻿# Урок 10\. Паттерн 2\. Функции с переменным количеством аргументов

Классическими примерами, приводимыми во многих статьях по проблемам переноса программ на 64\-битные системы, является некорректное использование функций printf, scanf и их разновидностей\.

Пример 1:

```cpp
const char *invalidFormat = "%u";
size_t value = SIZE_MAX;
printf(invalidFormat, value);
```

Пример 2:

```cpp
char buf[9];
sprintf(buf, "%p", pointer);
```

В первом случае не учитывается, что тип [size\_t](https://pvs-studio.ru/ru/blog/terms/0044/) не эквивалентен типу unsigned на 64\-битной платформе\. Это приведет к выводу на печать некорректного результата в случае, если value \> UINT\_MAX\.

Во втором случае автор кода не учел, что размер указателя в будущем может составить более 32 бит\. В результате на 64\-битной архитектуре данный код приведет к переполнению буфера\.

Некорректное использование функций с перемененным количеством параметров является распространенной ошибкой на всех архитектурах, а не только 64\-битных\. Это связано с принципиальной опасностью использования данных конструкций языка Си\+\+\. Общепринятой практикой является отказ от них и использование безопасных методик программирования\. Мы настоятельно рекомендуем модифицировать код и использовать безопасные методы\. Например, можно заменить printf на cout, а sprintf на boost::format или std::stringstream\.

Данную рекомендацию часто критикуют разработчики под Linux, аргументируя тем, что gcc проверяет соответствие строки форматирования фактическим параметрам, передаваемым в функцию printf\. Однако они забывают, что строка форматирования может передаваться из другой части программы, загружаться из ресурсов\. Другими словами, в реальной программе строка форматирования редко присутствует в явном виде в коде, и, соответственно, компилятор не может ее проверить\. Если же разработчик использует Visual Studio 2005/2008, то он не сможет получить предупреждение на код вида "void \*p \= 0; printf\("%x", p\);", даже используя ключи /W4 и /Wall\.

Для работы с memsize\-типами в функциях вида sscanf, printf имеются спецификаторы размера\. Если вы разрабатываете Windows\-приложение, то вы можете использовать спецификатор размера "I"\. Пример использования:

```cpp
size_t s = 1; 
printf("%Iu", s);
```

Если вы разрабатываете приложение под Linux, то вам будет доступен спецификатор размера "z"\. Пример использования:

```cpp
size_t s = 1;
printf("%zu", s);
```

Спецификаторы хорошо описаны в статье Wikipedia "[printf](https://ru.wikipedia.org/wiki/Printf)"\.

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

```cpp
// PR_SIZET on Win64 = "I"
// PR_SIZET on Win32 = ""
// PR_SIZET on Linux64 = "z"
// ...
size_t u;
scanf("%" PR_SIZET "u", &u);
```

Рассмотрим еще один пример\. Хотя этот пример выглядит наиболее странно, код, который приведен здесь в упрощенном виде, использовался в реальном приложении в подсистеме UNDO/REDO:

```cpp
// Здесь указатели сохранялись в виде строки
int *p1, *p2;
....
char str[128];
sprintf(str, "%X %X", p1, p2);
// А в другой функции данная строка
// обрабатывалась следующим образом:
void foo(char *str)
{
  int *p1, *p2;
  sscanf(str, "%X %X", &p1, &p2);
  // Результат - некорректное значение указателей p1 и p2.
  ...
}
```

Результатом манипуляций указателями с использованием %X стало некорректное поведение программы на 64\-битной системе\. Данный пример показывает, как опасны потаенные дебри больших и сложных проектов, которые пишутся многими годами\. Если проект достаточно велик и стар, то в нем можно встретить очень интересные фрагменты, подобные этому\.

## Диагностика

Опасность для функций с переменным количеством аргументов представляют типы, меняющие свой размер на 64\-битной системе, то есть [memsize](https://pvs-studio.ru/ru/blog/terms/0030/) типы\. Статический анализатор [PVS\-Studio](https://pvs-studio.ru/ru/pvs-studio/) предупреждает об использовании таких типов диагностическим сообщением [V111](https://pvs-studio.ru/ru/docs/warnings/v111/)\.

Если типы аргументов не изменили своей разрядности, то код считается корректным и предупреждающих сообщений выдано не будет\. Пример корректного кода с точки зрения анализатора:

```cpp
printf("%d", 10*5);
CString str;
size_t n = sizeof(float);
str.Format(StrFormat, static_cast<int>(n));
```

_Авторы курса: Андрей Карпов \([karpov@viva64\.com](mailto:karpov@viva64.com)\), Евгений Рыжков \([evg@viva64\.com](mailto:evg@viva64.com)\)\._

_Правообладателем курса "Уроки разработки 64\-битных приложений на языке Си/Си\+\+" является ООО "Системы программной верификации"\. Компания занимается разработкой программного обеспечения в области анализа исходного кода программ\. [Сайт компании](https://pvs-studio.ru/ru/pvs-studio/)\._