Как передать переменное количество аргументов в printf / sprintf

83

У меня есть класс, содержащий функцию «ошибки», которая форматирует некоторый текст. Я хочу принять переменное количество аргументов, а затем отформатировать их с помощью printf.

Пример:

class MyClass
{
public:
    void Error(const char* format, ...);
};

Метод Error должен принимать параметры, вызывать printf / sprintf для его форматирования, а затем что-то делать с ним. Я не хочу писать все форматирование самостоятельно, поэтому имеет смысл попытаться выяснить, как использовать существующее форматирование.

user5722
источник

Ответы:

152
void Error(const char* format, ...)
{
    va_list argptr;
    va_start(argptr, format);
    vfprintf(stderr, format, argptr);
    va_end(argptr);
}

Если вы хотите манипулировать строкой перед ее отображением и действительно хотите сначала сохранить ее в буфере, используйте vsnprintfвместо vsprintf. vsnprintfпредотвратит случайную ошибку переполнения буфера.

Джон Кугельман
источник
37

взгляните на vsnprintf, так как он будет делать то, что вы хотите http://www.cplusplus.com/reference/clibrary/cstdio/vsprintf/

вам нужно сначала инициализировать массив va_list arg, а затем вызвать его.

Пример из этой ссылки: / * пример vsprintf * /

#include <stdio.h>
#include <stdarg.h>

void Error (char * format, ...)
{
  char buffer[256];
  va_list args;
  va_start (args, format);
  vsnprintf (buffer, 255, format, args);


  //do something with the error

  va_end (args);
}
Lodle
источник
6
Второй аргумент vsnprintf должен быть длиной буфера, включая завершающий нулевой байт ('\ 0'). Таким образом, вы можете использовать 256 в вызове функции вместо 255.
aviggiano
и передача магических чисел - ПЛОХО ... используйте sizeof(buffer)вместо 256.
Anonymouse
4

Я должен был прочитать больше о существующих вопросах в переполнении стека.

С ++ Передача переменного числа аргументов - аналогичный вопрос. У Майка Ф. есть следующее объяснение:

Невозможно вызвать (например) printf, не зная, сколько аргументов вы ему передаете, если вы не хотите использовать непереносимые трюки.

Обычно используемое решение - всегда предоставлять альтернативную форму функций vararg, поэтому printf имеет vprintf, который принимает va_list вместо .... Версии ... - это просто оболочки для версий va_list.

Это именно то, что я искал. Я выполнил тестовую реализацию вот так:

void Error(const char* format, ...)
{
    char dest[1024 * 16];
    va_list argptr;
    va_start(argptr, format);
    vsprintf(dest, format, argptr);
    va_end(argptr);
    printf(dest);
}
user5722
источник
Последний 'printf (dest);' имеет неправильный формат - ему тоже нужна как минимум строка формата.
Jonathan Leffler
Это не так, поскольку строка является строкой формата, т.е. printf ("строка"); в порядке
Lodle
4
Вы можете уйти с printf (dest) до тех пор, пока dest не будет содержать "% s" или "% d", а затем BOOM . Используйте printf ("% s", dest).
John Kugelman
Просто хочу указать, что дамп ядра - лучший сценарий, сделайте это в коде сервера, и хакеры получат ваш процессор на завтрак.
MickLH
Попробуйте использовать "% .16383s", и это защитит массив dest от переполнения. (с учетом терминатора '\ 0')
eddyq
3

Вы ищете вариативные функции . printf () и sprintf () - это функции с переменным числом аргументов - они могут принимать переменное количество аргументов.

В основном это влечет за собой следующие шаги:

  1. Первый параметр должен указывать на количество следующих параметров. Таким образом, в printf () параметр «format» дает это указание - если у вас есть 5 спецификаторов формата, тогда он будет искать еще 5 аргументов (всего 6 аргументов). Первый аргумент может быть целым числом (например, «myfunction (3, a, b, c) "где" 3 "означает" 3 аргумента)

  2. Затем выполните цикл и получите каждый последующий аргумент, используя функции va_start () и т. Д.

Есть много руководств о том, как это сделать - удачи!

poundifdef
источник
3

Использование функций с эллипсами не очень безопасно. Если производительность не критична для функции журнала, рассмотрите возможность использования перегрузки оператора, как в boost :: format. Вы могли написать что-то вроде этого:

#include <sstream>
#include <boost/format.hpp>
#include <iostream>
using namespace std;

class formatted_log_t {
public:
    formatted_log_t(const char* msg ) : fmt(msg) {}
    ~formatted_log_t() { cout << fmt << endl; }

    template <typename T>
    formatted_log_t& operator %(T value) {
        fmt % value;
        return *this;
    }

protected:
    boost::format                fmt;
};

formatted_log_t log(const char* msg) { return formatted_log_t( msg ); }

// use
int main ()
{
    log("hello %s in %d-th time") % "world" % 10000000;
    return 0;
}

В следующем примере показаны возможные ошибки с помощью эллипсов:

int x = SOME_VALUE;
double y = SOME_MORE_VALUE;
printf( "some var = %f, other one %f", y, x ); // no errors at compile time, but error at runtime. compiler do not know types you wanted
log( "some var = %f, other one %f" ) % y % x; // no errors. %f only for compatibility. you could write %1% instead.
Кирилл Васильевич Лядвинский
источник
5
Вот как сделать легкое трудным.
eddyq
2
«Использование функций с эллипсами не очень безопасно». если ваша единственная безопасная альтернатива включает в себя c ++ и boost, вы должны объяснить, что вы имеете в виду под «не очень безопасным», и упомянуть, что функции printf совершенно безопасны, если вы используете правильные спецификаторы формата.
osvein
2

Простой пример ниже. Обратите внимание, что вы должны передать буфер большего размера и проверить, достаточно ли он большой или нет.

void Log(LPCWSTR pFormat, ...) 
{
    va_list pArg;
    va_start(pArg, pFormat);
    char buf[1000];
    int len = _vsntprintf(buf, 1000, pFormat, pArg);
    va_end(pArg);
    //do something with buf
}
DougN
источник