Home > Software design >  Use printf with args into variadic functions?
Use printf with args into variadic functions?

Time:12-19

I need a function that works like printf, but makes some changes to the fmt string: for example, add at the beginning a string containing the datetime, but the rest, I will maintain the same printf stuff...

void simple_printf(const char* fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    va_end(args);

    /* made some changes to fmt, concatenate string,...*/

    printf(fmt, ...);
}

Here is the code I'm making. As you can see, I wish to change the fmt string, but after, call the 'standard' printf or sprintf, passing the parameters — a sort of bypass.

Is this possible?

CodePudding user response:

There are various ways of doing this. You can find some of my code that does this sort of thing in my SOQ (Stack Overflow Questions) repository on GitHub as files stderr.c and stderr.h in the src/libsoq sub-directory. That's a package I've developed over many years (the earliest version I still have a record for dates to 1988) and I use it in most of my C programs.

The scheme used now ensures that there's a single write operation by converting the data to be formatted into a string — see err_fmtmsg() — and then using the appropriate writing mechanism (standard I/O such as fprintf(), or write(), or syslog()) to send the message to the output mechanism.

static size_t err_fmtmsg(char *buffer, size_t buflen, int flags, int errnum,
                         const char *format, va_list args)
{
    char *curpos = buffer;
    char *bufend = buffer   buflen;

    buffer[0] = '\0';   /* Not strictly necessary */
    if ((flags & ERR_NOARG0) == 0)
        curpos = efmt_string(curpos, bufend, "%s: ", arg0);
    if (flags & ERR_LOGTIME)
    {   
        char timbuf[32];
        curpos = efmt_string(curpos, bufend,
                             "%s - ", err_time(flags, timbuf, sizeof(timbuf)));
    }   
    if (flags & ERR_PID)
        curpos = efmt_string(curpos, bufend,
                             "pid=%d: ", (int)getpid());
    curpos = vfmt_string(curpos, bufend, format, args);
    if (flags & ERR_ERRNO)
        curpos = efmt_string(curpos, bufend,
                             "error (%d) %s\n", errnum, strerror(errnum));
    assert(curpos >= buffer);
    return((size_t)(curpos - buffer));
}

As you may be able to see, this can prefix the message produced by arg0 (the program name, set via a function err_setarg0(); it can add a PID; it can add the timestamp (with options for integral seconds, milliseconds, microseconds, nanoseconds under control of the flags), and can append the error number and corresponding system error message too.

This is a function hidden in the bowels of the system. At the external level, one of the entry points is extern void err_syserr(const char *fmt, ...); — this automatically adds the system error, prints the message on standard error, and exits the program. There are a wide variety of other entry points for logging, some of which exit and some return. And there are a lot of controls too.

Note that the err_fmtmsg() function takes an argument va_list args. This is normally the best way to work. You should write your code using this scheme:

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

void simple_vprintf(const char* fmt, va_list args)
{
    /* … preamble … */
    vprintf(fmt, args);
    /* … postamble … */
}

You write the main function using the va_list function and provide a convenience interface using ... that calls on the main function, as shown above.

If you are going to use multiple calls to standard I/O writing functions (fprintf() et al), then consider using flockfile() and funlockfile() to keep the outputs 'atomic'.

void simple_vprintf(const char* fmt, va_list args)
{
    flockfile(stdout);
    /* … preamble — possibly writing to stdout … */
    vprintf(fmt, args);
    /* … postamble — possibly writing to stdout … */
    funlockfile(stdout);
}

You'd also consider providing functions like these:

extern void simple_fprintf(FILE *fp, const char *fmt, ...);
extern void simple_vfprintf(FILE *fp, const char *fmt, va_list args);

Then simple_vprintf() would simply invoke simple_vfprintf(stdout, fmt, args). You might then start looking at static inline implementations of the cover functions in the header file.

After a while, if you implement enough variations, you're starting to encroach on the implementation found in stderr.[ch].

CodePudding user response:

Instead of calling printf, you should call vprintf from your variadic function:

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

void simple_printf(const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);

    /* made some changes to fmt, concatenate string, ... */

    vprintf(fmt, args);
    va_end(args);
}
  • Related