143

I have a function that accepts a string, that is:

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?


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?

nbro
  • 15,395
  • 32
  • 113
  • 196
pistacchio
  • 56,889
  • 107
  • 278
  • 420
  • 1
    I trust that the function prototype is really: extern void log_out(const char *, ...); because if it isn't, the call to it is erroneous (too many arguments). It should be taking a const pointer because there is no reason for log_out() to modify the string. Of course, you might be saying that you want to pass a single string to the function - but can't. One option is then to write a varargs version of the log_out() function. – Jonathan Leffler Apr 29 '09 at 22:51

8 Answers8

128

Use sprintf. (This is NOT safe, but OP asked for an ANSI C answer. See the comments for a safe version.)

int sprintf ( char * str, const char * format, ... );

Write formatted data to string Composes a string with the same text that would be printed if format was used on printf, but instead of being printed, the content is stored as a C string in the buffer pointed by str.

The size of the buffer should be large enough to contain the entire resulting string (see snprintf for a safer version).

A terminating null character is automatically appended after the content.

After the format parameter, the function expects at least as many additional arguments as needed for format.

Parameters:

str

Pointer to a buffer where the resulting C-string is stored. The buffer should be large enough to contain the resulting string.

format

C string that contains a format string that follows the same specifications as format in printf (see printf for details).

... (additional arguments)

Depending on the format string, the function may expect a sequence of additional arguments, each containing a value to be used to replace a format specifier in the format string (or a pointer to a storage location, for n). There should be at least as many of these arguments as the number of values specified in the format specifiers. Additional arguments are ignored by the function.

Example:

// Allocates storage
char *hello_world = (char*)malloc(13 * sizeof(char));
// Prints "Hello world!" on hello_world
sprintf(hello_world, "%s %s!", "Hello", "world");
Community
  • 1
  • 1
akappa
  • 10,220
  • 3
  • 39
  • 56
  • 46
    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 --- ex-moderator kitten Apr 29 '09 at 21:40
  • 7
    Yes, but he asked for an ANSI C function and I'm not so sure if snprintf is ansi or even posix. – akappa Apr 29 '09 at 21:59
  • 8
    Ah. I missed that. But I'm rescued by the new standard: the 'n' variants are official in C99. FWIW, YMMV, etc. – dmckee --- ex-moderator kitten Apr 29 '09 at 22:04
  • 1
    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 Apr 29 '09 at 23:17
  • 2
    @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 Apr 30 '09 at 01:08
  • 1
    From the theory's point of view, this is a nice answer, but please provide also simple examples that put in practice the theory. – nbro Mar 28 '16 at 12:01
  • @nbro: just use `sprintf` the way you would use `printf`, the only difference being the first additional string buffer argument. E.g.: `printf("Hi, I've got %d apples\n", apples)` vs `sprintf(out_string, "Hi, I've got %d apples\n", apples)`. If you don't care about ansi and you are willing to use C99, use snprintf: `snprintf(out_string, out_length, "Hi, I've got %d apples\n", apples)`. – akappa Mar 28 '16 at 14:52
  • @akappa Of course my suggestion was not exactly to help me but to help other newbie programmers eventually. But thanks anyway! – nbro Mar 28 '16 at 15:36
  • I know it's an old question but is there a reason why you malloc'd 13 chars instead of 12 (length of "Hello world!")? – user3932000 Sep 02 '20 at 09:18
  • 1
    @user3932000 Because `sprintf` always null-terminates and that requires extra space. – Yamirui Sep 17 '20 at 10:26
27

If you have a POSIX-2008 compliant system (any modern Linux), you can use the safe and convenient asprintf() function: It will malloc() enough memory for you, you don't need to worry about the maximum string size. Use it like this:

char* string;
if(0 > asprintf(&string, "Formatting a number: %d\n", 42)) return error;
log_out(string);
free(string);

This is the minimum effort you can get to construct the string in a secure fashion. The sprintf() code you gave in the question is deeply flawed:

  • There is no allocated memory behind the pointer. You are writing the string to a random location in memory!

  • Even if you had written

    char s[42];
    

    you would be in deep trouble, because you can't know what number to put into the brackets.

  • Even if you had used the "safe" variant snprintf(), you would still run the danger that your strings gets truncated. When writing to a log file, that is a relatively minor concern, but it has the potential to cut off precisely the information that would have been useful. Also, it'll cut off the trailing endline character, gluing the next log line to the end of your unsuccessfully written line.

  • If you try to use a combination of malloc() and snprintf() to produce correct behavior in all cases, you end up with roughly twice as much code than I have given for asprintf(), and basically reprogram the functionality of asprintf().


If you are looking at providing a wrapper of log_out() that can take a printf() style parameter list itself, you can use the variant vasprintf() which takes a va_list as an argument. Here is a perfectly safe implementation of such a wrapper:

//Tell gcc that we are defining a printf-style function so that it can do type checking.
//Obviously, this should go into a header.
void log_out_wrapper(const char *format, ...) __attribute__ ((format (printf, 1, 2)));

void log_out_wrapper(const char *format, ...) {
    char* string;
    va_list args;

    va_start(args, format);
    if(0 > vasprintf(&string, format, args)) string = NULL;    //this is for logging, so failed allocation is not fatal
    va_end(args);

    if(string) {
        log_out(string);
        free(string);
    } else {
        log_out("Error while logging a message: Memory allocation failed.\n");
    }
}
cmaster - reinstate monica
  • 38,891
  • 9
  • 62
  • 106
  • 2
    Note that `asprintf()` is neither part of standard C 2011 nor part of POSIX, not even POSIX 2008 or 2013. It is part of TR 27431-2: see [Do you use the TR 24731 'safe' functions?](http://stackoverflow.com/questions/372980/do-you-use-the-tr-24731-safe-functions) – Jonathan Leffler May 24 '14 at 15:50
  • works like charm ! I was facing the issue, when "char*" value was not getting properly printed in logs, i.e. formatted string was not appropriate somehow. code was using, "asprintf()". – parasrish Jan 09 '16 at 19:38
13

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.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Michael Burr
  • 333,147
  • 50
  • 533
  • 760
6

Don't use sprintf.
It will overflow your String-Buffer and crash your Program.
Always use snprintf

Tom
  • 654
  • 6
  • 10
3

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
  • 730,956
  • 141
  • 904
  • 1,278
2

Verified and Summary:

sprintf vs asprintf

asprintf = malloc + sprintf

sample code

sprintf

int largeEnoughBufferLen = 20;

char *someStr = (char*)malloc(largeEnoughBufferLen * sizeof(char));

sprintf(someStr, "formatted string: %s %s!", "Hello", "world");

// do what you want for formatted string: someStr

free(someStr);

asprintf

char *someStr;

int formattedStrResult = asprintf(&someStr, "formatted string: %s %s!", "Hello", "world");

if(formattedStrResult > 0){
    // do what you want for formatted string: someStr

    free(someStr);
} else {
    // some error
}
Xiddoc
  • 3,369
  • 3
  • 11
  • 37
crifan
  • 12,947
  • 1
  • 71
  • 56
  • The author asked for an ANSI C answer. asprintf is not part of the C standard. We have to add a #define __GNU_SOURCE at beginning of the source file or a flag -D_GNU_SOURCE according to https://stackoverflow.com/questions/61306157/unable-to-compile-program-due-to-function-asprintf – Dimitri Lesnoff Jul 20 '22 at 09:45
0

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
  • 56,304
  • 9
  • 91
  • 158
-2

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
  • 19,853
  • 5
  • 45
  • 59