Як передати змінну кількість аргументів у printf / sprintf


83

У мене є клас, який містить функцію "помилка", яка буде форматувати деякий текст. Я хочу прийняти змінну кількість аргументів, а потім відформатувати їх за допомогою printf.

Приклад:

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

Метод помилки повинен враховувати параметри, викликати printf / sprintf, щоб відформатувати його, а потім щось робити з ним. Я не хочу писати все форматування самостійно, тому має сенс спробувати зрозуміти, як використовувати існуюче форматування.

Відповіді:


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

Якщо ви хочете маніпулювати рядком перед тим, як відображати його, і вам дійсно спочатку потрібно його зберегти в буфері, використовуйте vsnprintfзамість vsprintf. vsnprintfзапобіжить випадковій помилці переповнення буфера.


36

подивіться на vsnprintf, оскільки це буде робити те, що вам потрібно http://www.cplusplus.com/reference/clibrary/cstdio/vsprintf/

вам доведеться спочатку ініціювати масив аргументів va_list, а потім викликати його.

Приклад за цим посиланням: / * приклад 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);
}

6
Другим аргументом vsnprintf має бути довжина буфера, включаючи закінчувальний нульовий байт ('\ 0'). Тож ви можете використовувати 256 у виклику функції замість 255.
aviggiano

а передавати магічні числа - ПОРОШО ... використовувати sizeof(buffer)замість 256.
Анонім

4

Я мав би прочитати більше про існуючі питання в переповненні стека.

Подібне змінне число аргументів в C ++ - це подібне питання. Майк Ф має таке пояснення:

Неможливо зателефонувати (наприклад) 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);
}

Остаточний 'printf (dest);' неправильно сформований - йому також потрібен принаймні рядок форматування.
Джонатан Леффлер,

Це не так, оскільки рядок є рядком формату, тобто printf ("рядок"); це нормально
Лодл

4
Ви можете піти з printf (dest) до тих пір, поки dest не містить "% s" або "% d", потім BOOM . Будь ласка, використовуйте printf ("% s", dest).
Джон Кугельман,

Просто хочу зайти, щоб зазначити, що дамп ядра - найкращий сценарій, зробіть це в коді сервера, і хакери отримають ваш процесор на сніданок.
MickLH

Спробуйте використати "% .16383s", і це захистить масив dest від переповнення. (дозволити термінатор '\ 0')
eddyq

3

Ви шукаєте варіадичні функції . printf () і sprintf () є варіатичними функціями - вони можуть приймати змінну кількість аргументів.

Це в основному передбачає такі кроки:

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

  2. Потім прокрутіть і отримайте кожен наступний аргумент, використовуючи функції va_start () тощо.

Існує безліч підручників, як це зробити - удачі!


3

Використання функцій з еліпсами не надто безпечно. Якщо продуктивність не є критичною для функції журналу, розгляньте можливість використання перевантаження оператора, як у форматі boost ::. Ви можете написати щось подібне:

#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
}

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.