Реализовать проверку переменных аргументов для пользовательских функций форматирования строк - PullRequest
0 голосов
/ 04 января 2019

Visual Studio 2015 представила два новых предупреждения, C4473 и C4477, которые сообщают, когда функция форматирования строки имеет несоответствие между строкой форматирования и соответствующими переменными аргументами:

warning C4473: 'printf' : not enough arguments passed for format string
warning C4477: 'printf' : format string '%p' requires an argument of type 'void *', but variadic argument 1 has type 'int'

Эти предупреждения очень полезны инекоторое время поддерживались другими популярными компиляторами (gcc и clang, с опцией -wformat, я считаю, хотя я гораздо менее знаком с этими компиляторами).

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

Но ради этого вопроса давайте простоПредположим, я просто заключаю вызов в printf:

void Log(const char * format, ...)
{
    va_list args;
    va_start(args, format);
    printf(format, args);
    va_end(args);
}

При этом у меня не отображаются предупреждения, если я вызываю функцию Log с несовпадающими аргументами:

printf("Error: %p\n", 'a'); // warning C4477
printf("Error: %p\n");      // warning C4473
Log("Error: %p\n", 'a');    // no warning
Log("Error: %p\n");         // no warning

Есть ли способ сообщить компилятору, что он должен проверять переменные аргументы моей функции так же, как это делает с printf?Особенно для компилятора MSVC, но решение, которое работает для gcc и clang, тоже будет оценено.

Ответы [ 2 ]

0 голосов
/ 10 февраля 2019

Так что, похоже, мне действительно не повезло с Visual Studio.

Как упоминал Джонатан в своем ответе, это можно сделать как с GCC, так и с Clang.Это также объясняется в этом ответе .

Однако, хотя Visual Studio, похоже, выводит предупреждения для printf и ряда других стандартных функций, это более или менее жестко закодировано вкомпилятор и не распространяется на пользовательские функции.

Существует альтернатива, которую я решил не использовать (я объясню почему).Microsoft предоставляет так называемую аннотацию SAL (для языка аннотаций исходного кода).Можно аннотировать функцию такими вещами, как _Printf_format_string_, чтобы получить то, что я просил.Это описано, например, в этом ответе .

Недостатком является то, что по умолчанию компилятор полностью игнорирует его.Эти аннотации фактически оцениваются, только если вы включите Анализ кода, либо с помощью параметра /analysis, либо из окна свойств вашего проекта.Этот анализ делает много проверок;по умолчанию он использует Microsoft Native Recommended Rules, но можно настроить то, что нужно проверять, и даже перейти к проверке только на форматирование строки.

Но даже к этому моменту затраты времени на компиляцию для относительно небольшого проекта, такого как мой, делают его не стоящим.

0 голосов
/ 04 января 2019

Я не знаю, что доступно в VS 2015 или VS 2017 (полу-случайный поиск по в документации Microsoft не дал никакого освещения).Однако GCC и Clang поддерживают декларативный атрибут функции :

__attribute__((format(printf(,n,m)))

, который может быть преобразован в достаточно переносимый код, например:

#if !defined(PRINTFLIKE)
#if defined(__GNUC__)
#define PRINTFLIKE(n,m) __attribute__((format(printf,n,m)))
#else
#define PRINTFLIKE(n,m) /* If only */
#endif /* __GNUC__ */
#endif /* PRINTFLIKE */

…

extern NORETURN void err_abort(const char *format, ...) PRINTFLIKE(1,2);
extern NORETURN void err_error(const char *format, ...) PRINTFLIKE(1,2);

…

extern void err_logmsg(FILE *fp, int flags, int estat, const char *format, ...) PRINTFLIKE(4,5);
…
extern void err_remark(const char *format, ...) PRINTFLIKE(1,2);

PRINTFLIKE(n,m) макрос говорит, что строка формата printf() является аргументом n, а фактические аргументы начинаются с m.Большинство из них похожи на printf() со строкой формата в качестве первого аргумента и данными после.Функция err_logmsg() имеет больше параметров управления перед строкой форматирования в аргументе 4, но аргументы формата начинаются с 5, сразу после этого, как в fprintf() строка форматирования имеет аргумент 2, а аргументы начинаются с аргумента 3.

Было бы целесообразно разработать функцию с аргументами между строкой формата и списком переменных аргументов, например:

extern NORETURN void err_pos_error(const char *format, const char *filename, int lineno, const char *function, ...) PRINTFLIKE(1,5);

, которая может быть вызвана так:

err_pos_error("Failed to open file '%s': %d - %s\n", __FILE__, __LINE__, __func__, filename, errno, strerror(errno));

Мы можем обсудить, является ли это хорошим дизайном (вероятно, было бы лучше поместить аргументы __FILE__, __LINE__ и __func__ перед строкой форматирования, а не после, по разным причинам), но этовыполнимый дизайн, который демонстрирует непоследовательные числа в макросе PRINTFLIKE или использование __attribute__((format(printf,n,m))).

NORETURN - это поддержка макросов для определения функций, которые не возвращают:

#if !defined(NORETURN)
#if __STDC_VERSION__ >= 201112L
#define NORETURN      _Noreturn
#elif defined(__GNUC__)
#define NORETURN      __attribute__((noreturn))
#else
#define NORETURN      /* If only */
#endif /* __STDC_VERSION__ || __GNUC__ */
#endif /* NORETURN */

Код, на котором я основываюсь, доступен в моем репозитории SOQ (Вопросы о переполнении стека) на GitHub в виде файлов stderr.c и stderr.hв подкаталоге src / libsoq .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...