Аннотации sal (prefast) для принудительного применения числа переменных аргументов

У меня есть функция variadic:

print_n_integers(7, 1, 2, 3, 4, 5, 6, 7);

int print_n_integers( unsigned int count, ... )
{
    // use va_start etc.
}

Я хотел бы использовать аннотации Microsoft SAL от sal.h так что компилятор Visual Studio замечает, когда количество параметров не совпадает count, Я могу применить count как буквально так:

int print_n_integers (
    _Literal_ unsigned int count,
    ...
)

и Visual Studio, по крайней мере, имеет некоторый ум, чтобы справиться с printf(), но есть ли что-то для простого подсчета параметров?

2 ответа

Я хотел бы предложить альтернативный подход к вашей проблеме, который не использует статический анализ.

Даже если бы вы могли получить количество переменных аргументов для статического анализа, вам все равно пришлось бы указывать правильное количество для каждого вызова. Вы также должны убедиться, что все аргументы являются (или могут быть повышены до) целыми числами.

Более естественным, не вариадическим подходом для обработки списка гомогенных типов является печать массива:

    void print_nint(const int *arr, size_t n);

Совместимые с C99 компиляторы могут создавать массивы на месте с составными литералами:

    print_nint((int[]) {0, 8, 15}, 3);

Этот синтаксис можно обернуть в макрос:

    #define print_int(...)                                  \
        print_nint((int[]){__VA_ARGS__},                    \
            sizeof((int[]) {__VA_ARGS__}) / sizeof(int))

Этот макрос выполняет подсчет, и вы можете использовать его без явного подсчета аргументов:

    print_int(1, 2, 3.0, rand(), i++, j++);

Второе разложение переменных аргументов используется внутри sizeof и не оценивается. В строке выше, rand() вызывается только один раз и i а также j увеличиваются только один раз. Аргумент с плавающей точкой преобразуется в int перед печатью.

Visual Studio представила составные литералы в RC 2013, но более старые версии не поддерживают их. К сожалению, составные литералы также делают ваш код непригодным для компиляторов C++. В этих случаях вы можете переработать макрос, чтобы определить временный массив:

    #define print_int(...) {                                            \
        int PrintMe_[] = {__VA_ARGS__};                                 \
        print_nint(PrintMe_, sizeof(PrintMe_) / sizeof(*PrintMe_));     \
    }

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

Если вы используете только макрос, массив и его размер являются согласованными. Но вы можете опубликовать базовую реализацию и аннотировать ее с помощью SAL:

    void print_nint(_In_reads_(n) const int *arr, size_t n);

Я не защищаю безвозмездное использование макросов, но в этом случае макрос берет на себя роль шаблона; это может уменьшить опасную избыточность.

К сожалению, в настоящее время в SAL нет аннотации для этого случая.

Поддержка для printf/scanf/scanf_s класс функций жестко закодирован в анализаторах и доступен через _Printf_format_string_ / _Scanf_format_string_ / _Scanf_s_format_string_ аннотации соответственно (и для особых случаев, _Printf_format_string_params_ / _Scanf_format_string_params_ / _Scanf_s_format_string_params_). Таким образом, ваш единственный шанс - лоббировать команду анализа кода, чтобы добавить поддержку для вашего варианта использования.

Другие вопросы по тегам