tags:

views:

537

answers:

6

I have a function that accepts a string ( void log_out(char *); ).

In calling it, I need to create a formatted string on the fly like:

int i = 1;
log_out("some text%d", i);

How do I do this in ANSI C?

+10  A: 

Use sprintf.

akappa
Yikes! If at all possible use the 'n' variation functions. I.e. snprintf. They will make you count your buffer sizes and thereby insure against overruns.
dmckee
Yes, but he asked for an ANSI C function and I'm not so sure if snprintf is ansi or even posix.
akappa
Ah. I missed that. But I'm rescued by the new standard: the 'n' variants are official in C99. FWIW, YMMV, etc.
dmckee
the snprintf aren't the safest way to go about. You should go with snprintf_s. See http://msdn.microsoft.com/en-us/library/f30dzcf6(VS.80).aspx
Joce
@Joce - the C99 snprintf() family of functions is pretty safe; however the snprintf_s() family does have soem different behavior (especially regarding how trunction is handled). BUT - Microsoft's _snprintf() function is not safe - as it may potentially leave the resulting buffer unterminated (C99 snprintf() always terminates).
Michael Burr
A: 

Only, since sprintf() returns a int, this means that I have to write at least 3 commands, like:

   char *s;
   sprintf(s,"%d\t%d", ix, iy)
   log_out( s );

Any way to shorten this?

pistacchio
Why do you need to shorten it?
Chad Birch
Yikes, you are going to allocate some memory for s aren't you?
Simon Nickerson
sorry, i meant char s[20];
pistacchio
On most computers, 20 chars is insufficient space for two ints and a tab. Like dmckee said in his comment on akappa's post, use snprintf instead.
chazomaticus
A: 

I haven't done this, so I'm just going to point at the right answer.

C has provisions for functions that take unspecified numbers of operands, using the <stdarg.h> header. You can define your function as void log_out(const char *fmt, ...);, and get the va_list inside the function. Then you can allocate memory and call vsprintf() with the allocated memory, format, and va_list.

Alternately, you could use this to write a function analogous to sprintf() that would allocate memory and return the formatted string, generating it more or less as above. It would be a memory leak, but if you're just logging out it may not matter.

David Thornley
+4  A: 

It sounds to me like you want to be able to easily pass a string created using printf-style formatting to the function you already have that takes a simple string. You can create a wrapper function using stdarg.h facilities and vsnprintf() (which may not be readily available, depending on your compiler/platform):

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

// a function that accepts a string:

void foo( char* s);

// You'd like to call a function that takes a format string 
//  and then calls foo():

void foofmt( char* fmt, ...)
{
    char buf[100];     // this should really be sized appropriately
                       // possibly in response to a call to vsnprintf()
    va_list vl;
    va_start(vl, fmt);

    vsnprintf( buf, sizeof( buf), fmt, vl);

    va_end( vl);

    foo( buf);
}



int main()
{
    int val = 42;

    foofmt( "Some value: %d\n", val);
    return 0;
}

For platforms that don't provide a good implementation (or any implementation) of the snprintf() family of routines, I've successfully used a nearly public domain snprintf() from Holger Weiss.

Michael Burr
A: 

http://www.gnu.org/software/hello/manual/libc/Variable-Arguments-Output.html gives the following example to print to stderr. You can modify it to use your log function instead:

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

 void
 eprintf (const char *template, ...)
 {
   va_list ap;
   extern char *program_invocation_short_name;

   fprintf (stderr, "%s: ", program_invocation_short_name);
   va_start (ap, template);
   vfprintf (stderr, template, ap);
   va_end (ap);
 }

Instead of vfprintf you will need to use vsprintf where you need to provide an adequate buffer to print into.

lothar
+1  A: 

If you have the code to log_out(), rewrite it. Most likely, you can do:

static FILE *logfp = ...;

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

    va_start(args, fmt);
    vfprintf(logfp, fmt, args);
    va_end(args);
}

If there is extra logging information needed, that can be printed before or after the message shown. This saves memory allocation and dubious buffer sizes and so on and so forth. You probably need to initialize logfp to zero (null pointer) and check whether it is null and open the log file as appropriate - but the code in the existing log_out() should be dealing with that anyway.

The advantage to this solution is that you can simply call it as if it was a variant of printf(); indeed, it is a minor variant on printf().

If you don't have the code to log_out(), consider whether you can replace it with a variant such as the one outlined above. Whether you can use the same name will depend on your application framework and the ultimate source of the current log_out() function. If it is in the same object file as another indispensable function, you would have to use a new name. If you cannot work out how to replicate it exactly, you will have to use some variant like those given in other answers that allocates an appropriate amount of memory.

void log_out_wrapper(const char *fmt, ...)
{
    va_list args;
    size_t  len;
    char   *space;

    va_start(args, fmt);
    len = vsnprintf(0, 0, fmt, args);
    va_end(args);
    if ((space = malloc(len + 1)) != 0)
    {
         va_start(args, fmt);
         vsnprintf(space, len+1, fmt, args);
         va_end(args);
         log_out(space);
         free(space);
    }
    /* else - what to do if memory allocation fails? */
}

Obviously, you now call the log_out_wrapper() instead of log_out() - but the memory allocation and so on is done once. I reserve the right to be over-allocating space by one unnecessary byte - I've not double-checked whether the length returned by vsnprintf() includes the terminating null or not.

Jonathan Leffler