100

I have a class that holds an "error" function that will format some text. I want to accept a variable number of arguments and then format them using printf.

Example:

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

The Error method should take in the parameters, call printf/sprintf to format it and then do something with it. I don't want to write all the formatting myself so it makes sense to try and figure out how to use the existing formatting.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
user5722
  • 1,264
  • 3
  • 10
  • 13

7 Answers7

186

Use vfprintf, like so:

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

This outputs the results to stderr. If you want to save the output in a string instead of displaying it use vsnprintf. (Avoid using vsprintf: it is susceptible to buffer overflows as it doesn't know the size of the output buffer.)

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
  • I can't quite follow your code sample in comparison to your explanation. Why are you make use of `vfprintf` while explaining to use `vsnprintf` over `vsprintf`? – woodz Dec 28 '22 at 20:59
  • @woodz That's fair, my answer suffered from revision-itis. I've edited it to make it flow better. Does that help? – John Kugelman Dec 28 '22 at 23:35
50

have a look at vsnprintf as this will do what ya want http://www.cplusplus.com/reference/clibrary/cstdio/vsprintf/

you will have to init the va_list arg array first, then call it.

Example from that link: /* vsprintf example */

#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
  • 31,277
  • 19
  • 64
  • 91
  • 7
    [vsnprintf](http://linux.die.net/man/3/vsnprintf)'s second argument should be the buffer length, including the terminating null byte ('\0'). So you can use 256 in the function call instead of 255. – aviggiano Mar 23 '16 at 18:43
  • and passing magic numbers is BAD... use `sizeof(buffer)` instead of 256. – Anonymouse Mar 05 '19 at 14:16
5

I should have read more on existing questions in stack overflow.

C++ Passing Variable Number of Arguments is a similar question. Mike F has the following explanation:

There's no way of calling (eg) printf without knowing how many arguments you're passing to it, unless you want to get into naughty and non-portable tricks.

The generally used solution is to always provide an alternate form of vararg functions, so printf has vprintf which takes a va_list in place of the .... The ... versions are just wrappers around the va_list versions.

This is exactly what I was looking for. I performed a test implementation like this:

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);
}
Community
  • 1
  • 1
user5722
  • 1,264
  • 3
  • 10
  • 13
  • The final 'printf(dest);' is mal-formed - it needs at least a format string too. – Jonathan Leffler Jun 29 '09 at 03:17
  • It doesnt as the string is the format string i.e. printf("a string"); is fine – Lodle Jun 29 '09 at 03:22
  • 4
    You can get away with printf(dest) up until dest happens to contain "%s" or "%d", then *BOOM*. Please use printf("%s", dest). – John Kugelman Jun 29 '09 at 03:27
  • Just want to come by to point out that a core dump is the best case scenario, do that in server code and hackers will have your CPU for breakfast. – MickLH Feb 06 '14 at 20:03
  • Try using "%.16383s" and that will protect the array dest from overflow. (allow for the '\0' terminator) – eddyq Jun 13 '16 at 17:17
4

You are looking for variadic functions. printf() and sprintf() are variadic functions - they can accept a variable number of arguments.

This entails basically these steps:

  1. The first parameter must give some indication of the number of parameters that follow. So in printf(), the "format" parameter gives this indication - if you have 5 format specifiers, then it will look for 5 more arguments (for a total of 6 arguments.) The first argument could be an integer (eg "myfunction(3, a, b, c)" where "3" signifies "3 arguments)

  2. Then loop through and retrieve each successive argument, using the va_start() etc. functions.

There are plenty of tutorials on how to do this - good luck!

poundifdef
  • 18,726
  • 23
  • 95
  • 134
2

Simple example below. Note you should pass in a larger buffer, and test to see if the buffer was large enough or not

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
  • 4,407
  • 11
  • 56
  • 81
0

Have a look at the example http://www.cplusplus.com/reference/clibrary/cstdarg/va_arg/, they pass the number of arguments to the method but you can ommit that and modify the code appropriately (see the example).

stefanB
  • 77,323
  • 27
  • 116
  • 141
0

Using functions with the ellipses is not very safe. If performance is not critical for log function consider using operator overloading as in boost::format. You could write something like this:

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

The following sample demonstrates possible errors with ellipses:

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.
Kirill V. Lyadvinsky
  • 97,037
  • 24
  • 136
  • 212
  • 8
    This is how to make an easy thing hard. – eddyq Jun 13 '16 at 17:09
  • 2
    "Using functions with ellipses is not very safe." if your only safe alternative involves c++ and boost, you should explain what you mean by "not very safe", and mention that the printf functions are perfectly safe if you use the correct format specifiers. – osvein Jul 27 '17 at 15:42