views:

631

answers:

4

Hi

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

+5  A: 

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.

paxdiablo
Agreed, since it avoids the nasty macro, but I don't think this helps the questioner with the problem regarding how big a buffer to allocate.
Steve Jessop
Your edit renders my previous comment false :-)
Steve Jessop
Yeah, I originally misread the question, assuming that, like sprintf(), the caller of myprintf() would have a big enough buffer already.
paxdiablo
"Since printf() returns the number of characters written, we then used that to allocate a buffer", this was the key :-) However using vararg seems to be a better solution.
Johan
This may be platform-specific, but sprintf(NULL, fmt, ...) will return the number of characters necessary to malloc. Alternatively, sprintf() into a static buffer, and if that fills up the entire buffer, only /then/ use the double-sprintf trick. You'll amortize down to 1.<small> printfs on average
Tom
Try vsnprintf() with varargs. Be advised: Microsoft C++ (2003) had some very non-conforming C99-standard-breaking ideas about what [v]snprintf() was supposed to return.
Mr.Ree
+7  A: 

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 :-)

Steve Jessop
sizeof(char)==1 always, interesting. However I always put it there so that I do not forget it in the other cases (where it is important).
Johan
Also, first snprintf into a buffer and printf the buffer afterwards. That way the string is only formatted once.
David Schmitt
@David - I think you mean sprintf(), then puts(). Interestingly enough, GCC definitely does optimize printf("%s", foo) into puts() when it can.
Tom
Try vsnprintf() with varargs. Be advised: Microsoft C++ (2003) had some very non-conforming C99-standard-breaking ideas about what [v]snprintf() was supposed to return.
Mr.Ree
@mrree: Good point. Rather than "only have a C89 compiler", I should have said "only have a non-C99-compliant compiler". Some compilers conform to no known specification, especially if you accidentally compile C as C++ ;-)
Steve Jessop
+2  A: 

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

ephemient
+4  A: 

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.

qrdl
Typo: s/aspintf/asprintf/. I personally don't like the macro way of handling this, since it results in multiple evaluation of the arguments, but sure looks a lot simpler.
ephemient
Thanks for comments, typo corrected, macro changed to avoid multiple side effects.
qrdl
Sorry, one last nit. `printf(data)` is bad compared to `printf("%s", data)` or `puts(data)`: what if `data` contains a `%`?
ephemient
Yeah, I thought it wasn't necessary because `data` is not a user input but you are right - it still can contain format specifiers. Edited.
qrdl
asprintf() has become so much of a creature comfort that even HelenOS supports it natively in its printf sub system .. voted up, asprintf() is a much more elegant solution than trial and error with malloc() and realloc(). I also suggest an inline function for this one.
Tim Post
Just to reiterate, its not __just__ a GNU extension. :)
Tim Post