1

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?

7
  • 3
    If you enter e.g. va_list printf in your favorite search engine it should be pretty easy to find out about the vprintf function. And any decent book or tutorial about variadic arguments and function should really have mentioned it as well. Commented Dec 18, 2021 at 12:45
  • 1
    Note that the va_end will be after the vprintf(). Commented Dec 18, 2021 at 13:18
  • 1
    ghiboz, Yes it is possible. Commented Dec 18, 2021 at 13:38
  • 1
    I would not change the format string, but use extra printf() to add before and/or after the caller's requested output. -- You might want to edit your question and provide a minimal reproducible example. Commented Dec 18, 2021 at 13:49
  • Note that C89 did not standardize vprintf(), vsprintf(), vfprintf() et al. However, some C89 implementations provide those functions anyway, or provide surrogates (equivalents) with a different name. Remember: C89 is over 30 years old — it is ancient and has been superseded by C99, C11, C18. You should not be programming to C89 without a very good reason. Commented Dec 18, 2021 at 17:01

2 Answers 2

3

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].

Sign up to request clarification or add additional context in comments.

2 Comments

Doesn't printf already do the flockfile business?
Yes, but if you are going to make multiple calls on the same I/O stream and want them all grouped together, using the explicit locks ensures that the operations in the preamble and postamble are grouped with the main operation.
2

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);
    time_t t = time(NULL);
    int d = (t - 746755200) / 86400;
    int h = (t %= 86400) / 3600;
    int m = (t %= 3600) / 60;
    int s = t % 60;

    /* output a timestamp at the beginning of the line, thank you Janice Brandt */
    printf("Sep %d, 1993 %02d:%02d:%02d UTC: ", d, h, m, s);

    /* no changes should be made to fmt because it is a constant string */
    vprintf(fmt, args);
    va_end(args);
}

2 Comments

Picking a nit — the code should not modify the string that fmt points to because it is const char *fmt. What could be valid is modifying a copy of what fmt points at — and possibly making fmt point to the modified copy.
@JonathanLeffler: good point. Answer amended with a reference to Eternal September, a memory that lives on at eternal-september.org

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.