3
votes

I am playing with the printf and the idea to write a my_printf(...) that calls the normal printf and a sprintf that sends the result to a special function. (I was thinking about sprintf since that behaves just like printf on most platforms).

My idea was to write a small macro that did this:

#define my_printf(X, Y...) do{ printf(X, ## Y); \
    char* data = malloc(strlen(X)*sizeof(char)); \
    sprintf(data, X, ## Y); \
    other_print(data);\
    free(data);}while(0)

But since sprintf can expand the string to a much bigger size than X, this method breaks almost directly.

And just to add a number do the malloc seems to be the wrong way to attack the problem, since then I would just move the problem into the future and a day when I want print a big expression...

Does anyone has a better idea on how to attack this problem? Or how do I know how big the sprintf result will be?

Thanks Johan


Update: I forgot that printf returns how many chars it prints, and since I already is calling printf in the macro it was a very easy thing to add a int that saves the number.

#define buf_printf(X, Y...) do{ int len = printf(X, ## Y); \
    char* data = malloc((len+1)*sizeof(char)); \
    sprintf(data, X, ## Y); \
    other_print(data);\
    free(data);}while(0)

Update: I was thinking about this and maybe to use a normal function that looks a lot like what ephemient has suggested is a good idea. The key there seems to be the v-version of the different printf functions (vprintf, vsprintf and vsnprintf). Thanks for pointing that out.

Thanks again Johan

4
I know this is an old question, but the C FAQ has this exact question: c-faq.com/varargs/vprintf.html.Neil

4 Answers

5
votes

The best way to do this is with varargs. Create a function with the same prototype as printf() and use the varargs functions to pass data to sprintf to populate the desired buffer, the also pass that buffer to printf("%s") before returning.

A lot of the early implementations had a 4K limit on the lowest level printf() call but I would opt for more than that. You probably need to just set an upper limit and stick to it.

One trick we used in a logging system was to write the data using printf() to a /dev/null handle. Since printf() returns the number of characters written, we then used that to allocate a buffer. But that wasn't very efficient since it involved calling a printf()-type function twice.

8
votes

Use snprintf to calculate the size. From the man page:

" If the output was truncated due to this limit then the return value is the number of characters (not including the trailing '\0') which would have been written to the final string if enough space had been available "

snprintf is standard from C99. If you only have a C89 compiler then check the documentation: pre-standard versions might not return the value you want. Again according to the man page, glibc prior to version 2.1 used to return -1 if the output was truncated, not the required size.

By the way, sizeof(char) is defined to be 1, always, in every C implementation ever :-)

5
votes

Since you are on Linux I'd suggest to use asprintf() - it it GNU extension that allocates string for you. And thanks to C99 variadic macros you don't need to mess with varagrs.

So your macro would look like:

#define MY_PRINT(...) do { \
                          char *data; \
                          asprintf(&data, __VA_ARGS__); \
                          printf("%s", data); \
                          other_print(data); \
                          free(data); \
                      } while (0)

NB! This is C99 and GNU-only code

Edit: Now it will evaluate macro arguments just once so calling macro with something like ("%d", i++) will work correctly.

2
votes

If you're always running on a system with glibc (i.e. Linux and any other OS with GNU userland), asprintf acts just like sprintf but can automatically handle allocation itself.

int my_printf(const char *fmt, ...) {
    char *buf = NULL;
    int len;
    va_list ap;

    va_start(ap, &fmt);
    len = vasprintf(&buf, fmt, ap);
    va_end(ap);

    if (len < 0) {
        /* error: allocation failed */
        return len;
    }

    puts(buf);
    other_print(buf);

    free(buf);
    return len;
}

Pax's answer is more portable, but instead of printing to /dev/null, here's a better trick: by POSIX, snprintf can be given a NULL buffer and 0 size, and it'll return how much it would have written -- but obviously it won't actually write anything.

int my_printf(const char *fmt, ...) {
    char *buf;
    int len, len2;
    va_list ap;

    va_start(ap, &fmt);
    len = vsnprintf(NULL, 0, fmt, ap);
    va_end(ap);

    buf = malloc(len + 1);
    if (!buf) {
        /* error: allocation failed */
        return -1;
    }

    va_start(ap, &fmt);
    len2 = snprintf(buf, len + 1, fmt, ap);
    buf[len] = '\0';
    va_end(ap);

    /* has another thread been messing with our arguments?
       oh well, nothing we can do about it */
    assert(len == len2);

    puts(buf);
    other_printf(buf);

    free(buf);
    return len;
}

Well, as onebyone says, some old systems don't have a compliant snprintf. Can't win them all...