Повторное использование аргумента вариационной функции не работает

У меня есть функция, которая пытается записать материал на консоль, а также в файл журнала, но это не работает. Второе использование аргумента переменной длины дает мусор, записанный на консоль. Есть идеи?

    void logPrintf(const char *fmt, ...) {
        va_list ap;    // log to logfile
        va_start(ap, fmt);
        logOpen;
        vfprintf(flog, fmt, ap);
        logClose;
        va_end(ap);
        va_list ap2;   // log to console
        va_start(ap2, fmt);
        printf(fmt, ap2);
        va_end(ap2);
    }

Ответ 1

Исходный код не работает, потому что он пытается использовать printf(), где ему нужно использовать vprintf(). Принимая сомнительные точки, такие как выражения logOpen и logClose по номинальной стоимости (с учетом обозначений, предположительно это макросы, которые открывают и закрывают поток файлов flog), код должен быть:

void logPrintf(const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    logOpen;
    vfprintf(flog, fmt, ap);
    logClose;
    va_end(ap);
    va_list ap2;
    va_start(ap2, fmt);
    vprintf(fmt, ap2);
    va_end(ap2);
}

Нет особых требований использовать две отдельные переменные va_list; вполне нормально использовать тот же самый в два раза, пока вы используете va_end(), прежде чем снова использовать va_start().

void logPrintf(const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    logOpen;
    vfprintf(flog, fmt, ap);
    logClose;
    va_end(ap);
    va_start(ap, fmt);
    vprintf(fmt, ap);
    va_end(ap);
}

Когда значение va_list передается другой функции (vfprintf() и vprintf() в этом коде), вы должны предположить, что она больше не может использоваться в текущей функции. Безопасно называть va_end() на нем.

В этом коде нет необходимости va_copy(). Он работает, но он не нужен. Вам нужно va_copy() в других обстоятельствах, например, когда ваша функция передается va_list, и вам нужно обработать список дважды:

void logVprintf(const char *fmt, va_list args1)
{
    va_list args2;
    va_copy(args2, args1);
    logOpen;
    vfprintf(flog, fmt, args1);
    logClose;
    vprintf(fmt, args2);
    va_end(args2);
}

Обратите внимание, что в этом коде на вызов va_end() на args1 отвечает код вызывающего кода. Действительно, в стандарте говорится:

Каждое обращение макросов va_start и va_copyдолжны совпадать с соответствующим вызовом макроса va_end в той же функции.

Так как функция logVprintf() не вызывает либо va_start, либо va_copy для инициализации args1, она не может законно называть va_end на args1. С другой стороны, стандарт требует, чтобы он вызывал va_end для args2.

Теперь функция logPrintf() может быть реализована в терминах logVprintf():

void logPrintf(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    logVprintf(fmt, args);
    va_end(args);
}

Эта структура - операционная функция, которая принимает va_list и функцию покрытия, которая принимает многоточие (переменные аргументы) и передает их в операционную функцию после преобразования в va_list - часто является хорошим способом работы. Рано или поздно вы обычно находите необходимость в версии с аргументом va_list.

Ответ 2

Обновите свой компилятор, что больше похоже на С++:

template <typename... Args>
void logPrintf(const char *fmt, Args&&... args) {
    logOpen;
    fprintf(flog, fmt, args...);
    logClose;

    printf(fmt, args...);
}

Хотя, конечно, было бы неплохо предоставить типовые версии printf и fprintf.

Ответ 3

Я думаю, что этот способ имеет смысл:

void logPrintf(const char *fmt, ...) {
        va_list ap;    // log to logfile
        va_start(ap, fmt);
        logOpen;
        vfprintf(flog, fmt, ap); //logfile
         printf(fmt, ap); //console
        logClose;
        va_end(ap);
    }