如何将可变数量的参数传递给printf / sprintf


83

我有一个类,其中包含一个“错误”函数,该函数将格式化某些文本。我想接受可变数量的参数,然后使用printf格式化它们。

例:

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

Error方法应该接受参数,调用printf / sprintf对其进行格式化,然后对其进行处理。我不想自己写所有格式,因此尝试弄清楚如何使用现有格式很有意义。

Answers:


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

如果要在显示字符串之前对其进行操作,并且确实确实需要首先将其存储在缓冲区中,请使用vsnprintf代替vsprintfvsnprintf将防止意外的缓冲区溢出错误。


36

看看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);
}

6
vsnprintf的第二个参数应为缓冲区长度,包括终止的空字节('\ 0')。因此,您可以在函数调用中使用256而不是
255。– aviggiano

并通过幻数是坏...使用sizeof(buffer),而不是256
Anonymouse

4

我应该阅读有关堆栈溢出中现有问题的更多信息。

C ++传递可变数量的参数是一个类似的问题。Mike F有以下解释:

在不知道要传递给它多少个参数的情况下,无法调用(例如)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(“ a string”); 很好
Lodle

4
您可以不使用printf(dest),直到dest碰巧包含“%s”或“%d”,然后是BOOM。请使用printf(“%s”,dest)。
约翰·库格曼

只是想指出核心转储是最好的情况,请在服务器代码中执行此操作,黑客将让您的CPU吃早饭。
MickLH 2014年

尝试使用“%.16383s”,这将防止阵列目标溢出。(允许使用'\ 0'终止符)
eddyq

3

您正在寻找可变参数函数。printf()和sprintf()是可变参数函数-它们可以接受可变数量的参数。

这基本上需要执行以下步骤:

  1. 第一个参数必须对随后的参数数量有所说明。因此,在printf()中,“ format”参数给出了这一指示-如果您有5个格式说明符,则它将查找另外5个参数(总共6个参数。)第一个参数可以是整数(例如“ myfunction” (3,a,b,c)”,其中“ 3”表示“ 3个自变量”

  2. 然后遍历并使用va_start()等函数检索每个连续的参数。

有很多关于如何执行此操作的教程-祝您好运!


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 '16

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
}

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.