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. Функции с переменным количеством аргументов.