# V111. Call of function 'foo' with variable number of arguments. N argument has memsize type.

Анализатор обнаружил потенциально возможную ошибку, связанную с передачей фактического аргумента типа memsize в функцию с переменным количеством аргументов. Потенциальная ошибка может заключаться в изменении требований, предъявляемых к функции на 64-битной системе.

Рассмотрим первый пример.

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

Данный код не учитывает, что тип 'size_t' не эквивалентен типу 'unsigned' на 64-битной платформе. Это приведет к выводу на печать некорректного результата, в случае если "value > UINT_MAX". Анализатор предупреждает вас, что в качестве фактического аргумента используется тип memsize. А это значит, что вам следует проверить строку 'invalidFormat', задающую формат вывода. Исправленный вариант может выглядеть, как показано ниже.

    const char *validFormat = "%Iu";
    size_t value = SIZE_MAX;
    printf(validFormat, value);

В коде реального приложения эта ошибка может встретиться, например, в следующем виде:

    wsprintf(szDebugMessage,
              _T("%s location %08x caused an access violation.\r\n"),
             readwrite,
             Exception->m_pAddr);

Второй пример.

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

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

    char buf[sizeof(pointer) * 2 + 1];
    sprintf(buf, "%p", pointer);
    // --- or ---
    std::stringstream s;
    s << pointer;

Третий пример.

    char buf[9];
    sprintf_s(buf, sizeof(buf), "%p", pointer);

Рассматривая второй пример, вы могли справедливо заметить, что для предотвращения переполнения следует использовать функции with security enhancements. В этом случае переполнение буфера не произойдет, но, к сожалению, и не будет получен корректный результат.

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

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

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

Примечание. Борьба с ложными срабатываниями при работе с функциями форматированного вывода

Диагностика V111 очень проста. Когда анализатор ничего не знает об эллипсис-функции, то он предупреждает о всех случаях, когда в функцию передается тип, меняющий свой размер. Когда информация о функции доступна анализатору, начинает работать более точная диагностика V576, и диагностика V111 не выдается. Если диагностика V576 отключена, то V111 выдается на всех функциях.

Соответственно, можно сократить количество ложных срабатываний, предоставив анализатору информацию о функциях форматирования. Анализатор сам знает о типовых функциях, таких как 'printf', 'sprintf' и так далее, поэтому речь сейчас идёт о разметке функций, реализованных самостоятельно. Как аннотировать функции рассказывается в описании диагностики V576.

Рассмотрим пример. Пользователь спрашивает: "Почему в случае N1 анализатор не выдает предупреждение V111, а в случае N2 выдаёт?".

    void OurLoggerFn(wchar_t const* const _Format, ...)
    {
      ....
    }
    void Foo(size_t length)
    {
      wprintf( L"%Iu", length );     // N1
      OurLoggerFn( L"%Iu", length ); // N2
    }

Дело в том, что анализатор знает, как работает стандартная функция 'wprintf'. А вот что такое 'OurLoggerFn' он не знает и на всякий случай предупреждает о передаче memsize-типа данных (в данном случае это тип 'size_t') в качестве фактического аргумента в эллипсис функцию.

Чтобы предупреждение V111 исчезло, следует проаннотировать функцию 'OurLoggerFn' следующим образом:

    //+V576, function:OurLoggerFn, format_arg:1, ellipsis_arg:2
    void OurLoggerFn(wchar_t const* const _Format, ...)
    .....

Дополнительные материалы по данной теме:

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