To output formatted debug output, I've written a wrapper for vsfprint. Now, I wanted to allocate exactly enough memory for the output buffer, instead of just claiming a random high buffer size (it's a small embedded platform (ESP8266)). For that I iterate through the variable arguments until a NULL is found.
This works fine, provided that I don't forget to add a (char *)NULL parameter to every call. So, I thought, let create another wrapper, a function that just relays all arguments and adds a (char *) NULL parameter:
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h> // malloc
void write_log(const char *format, ...) {
char* buffdyn;
va_list args;
// CALC. MEMORY
size_t len;
char *p;
if(format == NULL)
return;
len = strlen(format);
va_start(args, format);
while((p = va_arg(args, char *)) != NULL)
len += strlen(p);
va_end(args);
// END CALC. MEMORY
// ALLOCATE MEMORY
buffdyn = malloc(len + 1); /* +1 for trailing \0 */
if(buffdyn == NULL) {
printf("Not enough memory to process message.");
return;
}
va_start(args, format);
//vsnprintf = Write formatted data from variable argument list to sized buffer
vsnprintf(buffdyn, len, format, args);
va_end(args);
printf("%s\r\n",buffdyn);
free(buffdyn);
}
void write_log_wrapper(const char *format, ...) {
va_list arg;
va_start(arg, format);
write_log(format,arg,(char *)NULL);
va_end(arg);
}
int main()
{
const char* sDeviceName = "TEST123";
const char* sFiller1 = "12345678";
write_log_wrapper("Welcome to %s%s", sDeviceName,sFiller1);
write_log("Welcome to %s%s", sDeviceName,sFiller1, (char *)NULL);
return 0;
}
Calling the write_log() function directly works fine (if you don't forget the NULL parameter). Calling the write_log_wrapper() function will only display the first paramter, and then adds a "(nu" (garbage?) to the output.
What am I doing wrong? Is this a good way to approach what I'm aiming to do in the first place?
Thanks.
To determine how big a buffer is needed to hold the output string, you need to fully parse the entire format string and actually expand the arguments.
You can either do it yourself, duplicating all the processing of printf() and its ilk and hoping to not make any mistakes, or you can use vsnprintf() - first to determine the size, and then to actually expand the inputs to one output string.
#define FIXED_SIZE 64
void write_log(const char *format, ...)
{
// set up a fixed-size buffer and a pointer to it
char fixedSizeBuffer[ FIXED_SIZE ];
char *outputBuffer = fixedSizeBuffer;
// no dynamic buffer yet
char *dynamicBuffer = NULL;
// get the variable args
va_list args1;
va_start( args1, format );
// need to copy the args even though we won't know if we
// need them until after we use the first set
va_list args2;
va_copy( args2, args1 );
// have to call vsnprintf at least once - might as well use a small
// fixed-size buffer just in case the final string fits in it
int len = vsnprintf( fixedSizeBuffer, sizeof( fixedSizeBuffer ), format, args1 );
va_end( args1 );
// it didn't fit - get a dynamic buffer, expand the string, and
// point the outputBuffer pointer at the dynamic buffer so later
// processing uses the right string
if ( len > sizeof( fixedSizeBuffer ) )
{
dynamicBuffer = malloc( len + 1 );
vsnprintf( dynamicBuffer, len + 1, format, args2 );
outputBuffer = dynamicBuffer;
}
va_end( args2 );
// do something with outputBuffer
free( dynamicBuffer );
return;
}
What am I doing wrong?
Passing a va_list arg
write_log(format, arg, (char *)NULL);
is not the same as passing several char*
write_log("Welcome to %s%s", sDeviceName, sFiller1, (char *)NULL);
You won't get around to pass a sentinel marking the end of the parameters passed, that is a (char*) NULL or whatever you decide to use.
Alternatives would be to
pass the number of arguments explicitly, perhaps as 2nd parameter
parse the format string for conversion specifiers, in fact mimicking what printf does.
If you want just to ensure that all calls receive a setinel at the end, use a macro:
#define WRITE_LOG(...) write_log(__VA_ARGS__, (char*)0)
This ensures that there is always an extra 0 at the end.
Also be careful with NULL. It is underspecified in the C standard to what expressions this resolves. Common cases are 0 and (void*)0. So on 64bit architectures these may have different width (32 bit for the first, 64 bit for the second). It can be deadly for a variadic function to receive the wrong width here. Therefore I used (char*)0 which is the type that your function seems to expect. (But (void*)0 would also do in this special case.)
Related
With support for macOS, Windows (MSVC), and Linux, how do I do the following?
char *s;
func(&s, "foo");
if (<condition>) func(&s, "bar%s", "can")
/* want "foobarcan", and I don't know `strlen(s)` AoT */
I've tried with asprintf (was able to find an MSVC implementation) but that didn't seem to work well on this kind of workflow. fopencookie and funopen seem convenient but unavailable on MSVC.
Maybe there's some clean way with realloc to create a NUL ended char* in C?
As pointed out in the comments, (v)snprintf always returns the number of bytes that would have been written (excluding the null terminating byte), even if truncated. This has the effect that providing the function with a size argument of 0 returns the length of the to-be-formatted string.
Using this value, plus the string length of our existing string (if applicable), plus one, we (re)allocate the appropriate amount of memory.
To concatenate, simply print the formatted string at the correct offset.
An example, sans error checking.
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *dstr(char **unto, const char *fmt, ...) {
va_list args;
size_t base_length = unto && *unto ? strlen(*unto) : 0;
va_start(args, fmt);
/* check length for failure */
int length = vsnprintf(NULL, 0, fmt, args);
va_end(args);
/* check result for failure */
char *result = realloc(unto ? *unto : NULL, base_length + length + 1);
va_start(args, fmt);
/* check for failure*/
vsprintf(result + base_length, fmt, args);
va_end(args);
if (unto)
*unto = result;
return result;
}
int main(void) {
char *s = dstr(NULL, "foo");
dstr(&s, "bar%s%d", "can", 7);
printf("[[%s]]\n", s);
free(s);
}
stdout:
[[foobarcan7]]
The caveat here is that you can not write:
char *s;
dstr(&s, "foo");
s must be initialized as NULL, or the function must be used directly as an initializer, with the first argument set to NULL.
That, and the second argument is always treated as a format string. Use other means of preallocating the first string if it contains unsanitary data.
Example exploit:
/* exploit */
char buf[128];
fgets(buf, sizeof buf, stdin);
char *str = dstr(NULL, buf);
puts(str);
free(str);
stdin:
%d%s%s%s%s%d%p%dpapdpasd%d%.2f%p%d
Result: Undefined Behavior
TL;DR: mprintf("%s and %s", arg1, arg2) appears to print "arg1arg2 and arg2" instead of "arg1 and arg2" using va_* macros defined in stdarg.h
Hello, all. I'm using the functionality provided by vsprintf like so:
exits.c
#include "../../utils/printutil.c"
...
char dir[4][5];
char* format;
...
switch(bitcount) {
...
case 2: format = "%s and %s";
mprintf(format, dir[0], dir[1]);
break;
...
}
Note: dir gets its values from strcpy(dir[bitcount], names[dircount]);, where names is simply a char pointer array such that dir[0] = "North", dir[1] = "East", dir[2] = South", and dir[3] = "West".
printutil.c
/* Utils file to provide common utilities not tied specifically to any aspect of the software */
#include "printutil.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
char* mprintf(const char * format, ...) {
/* Prints a statement containing a format and then multiple arguments. */
char *buf;
va_list args;
va_start (args, format);
buf = malloc(sizeof(format) + sizeof(args));
vsprintf (buf, format, args);
va_end (args);
return buf;
}
Note: that printutil.h simply contains function prototypes
So that's the structure of the code. During a case of the switch statement, a format string is specified, and then mprintf() (defined in printutil.c) is called with the format and the args dir[0] and dir[1], two variables that were successfully written to earlier in exits.c.
Using gdb, I was able to discern that the values being passed to mprintf() were as expected:
Breakpoint 1, exitstr (exits=5 '\005') at exits.c:33
33 switch(bitcount) {
(gdb) s
38 case 2: format = "%s and %s";
(gdb) s
39 mprintf(format, dir[0], dir[1]);
(gdb) p format
$1 = 0x403115 "%s and %s"
(gdb) p dir[0]
$2 = "North"
(gdb) p dir[1]
$3 = "South"
When I step into the mprintf() function, gdb shows the contents of format are exactly as they should be, and shows the content of the va_list args as so:
17 vsprintf (buf, format, args);
(gdb) p format
$4 = 0x403115 "%s and %s"
(gdb) p args
$5 = {{gp_offset = 8, fp_offset = 48, overflow_arg_area = 0x7fffffffe710,
reg_save_area = 0x7fffffffe650}}
I built this code based on examples found in the cplusplus.com reference for vprintf and vsprintf and they both indicate that I have used the functions and the macros defined in stdarg.h correctly.
However, after stepping over the vsprintf() line, printing the contents of buf yields the source of my problems. Namely that the second argument is seemingly concatenated to the first, and then the second argument is re-used for the second format specifier.
(gdb) print buf
$7 = 0x63ca50 "NorthSouth and South"
Strangely, this only appears to happen when either 'North' or 'South' are the first arguments. If 'East' or 'West' are the first arguments, the arguments are printed to buf correctly.
Thank you all, in advance, for your time and patience.
buf = malloc(sizeof(format) + sizeof(args));
What is this supposed to do? sizeof (format) is just the size of a pointer, 4 bytes for 32-bit system or 8 bytes for 64-bit. sizeof (args) is just another name for sizeof (va_list), which is an implementation-defined type. However, you are using this as the expected size of a string.
Likely you will overflow this buffer and run into undefined behavior.
It's always better to use the variants of snprintf, which take a specified output buffer size.
Edit: Additionally, as #Mahonri noticed, you have put the string "North" into an array with space for only 5 characters and this discarded the terminating NUL byte. This caused sprintf to overrun the intended end of the string. I would have expected it to print NorthEast instead, but it's still only undefined behavior.
On recent enough systems, snprintf() tells you the number of bytes required even if it doesn't anything else. That makes your code as easy as
char* mprintfv(const char * format, va_list ap) {
// This version should always be given for the case someone wants to build on it.
va_list ap2;
va_copy(ap2, ap);
size_t length = vsprintf(NULL, format, ap2);
va_end(ap2);
if (size_t < 0) return NULL;
char *buf = malloc(length + 1);
if (!buf) return NULL;
vsprintf(buf, format, ap2);
return buf;
}
char* mprintf(const char * format, ...) {
/* Prints a statement containing a format and then multiple arguments. */
va_list args;
va_start (args, format);
char *buf = mprintfv(format, args);
va_end(args);
return buf;
}
On older systems, however, it might happen that sprintf() returns without telling how much space is needed. In this case, you'd have to create a loop which successively grows your memory block until it works, and resize it again.
I have the following code:
int ircsocket_print(char *message, ...)
{
char buffer[512];
int iError;
va_list va;
va_start(va, message);
vsprintf(buffer, message, va);
va_end(va);
send(ircsocket_connection, buffer, strlen(buffer), 0);
return 1;
}
And I wanted to know if this code is vulerable to buffer overflows by providing char arrays with a size > 512 to the variables list? And if so - How can I fix this?
thank you.
Yes, it is vulnerable.
You can implement your function this way:
int ircsocket_print(char *message, ...)
{
char buf[512];
char *buffer;
int len;
va_list va;
buffer = buf;
va_start(va, message);
len = vsnprintf(buffer, 512, message, va);
va_end(va);
if (len >= 512)
{
buffer = (char*)malloc(len + 1);
va_start(va, message);
len = vsnprintf(buffer, len + 1, message, va);
va_end(va);
}
send(ircsocket_connection, buffer, len, 0);
if (buffer != buf)
free(buffer);
return 1;
}
Yes, it is vulnerable.
Simply use vsnprintf instead:
vsnprintf(buffer, sizeof(buffer), message, va);
As everyone else noted, the basic answer to your question is "Yes, you are vulnerable to buffer overflows".
In C99, you could use a VLA:
void ircsocket_print(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
int len = vsnprintf(0, 0, message, args);
va_end(args);
char buffer[len+1];
va_start(args, fmt);
int len = vsnprintf(buffer, len+1, message, args);
va_end(args);
send(ircsocket_connection, buffer, len, 0);
}
Note the idiom that calls vsnprintf() once with length 0 (the second zero) to obtain the length required, then calls it a second time to format the data into the buffer. Also note the careful resetting of args after each call to vsnprintf(); that is required by the C standard:
ยง7.15 <stdarg.h>
If access to the varying arguments is
desired, the called function shall declare an object (generally referred to as ap in this
subclause) having type va_list. The object ap may be passed as an argument to
another function; if that function invokes the va_arg macro with parameter ap, the
value of ap in the calling function is indeterminate and shall be passed to the va_end
macro prior to any further reference to ap.
One downside to this formulation is that it takes a pessimistic view and unconditionally calls vsnprintf() twice. You might prefer to take an optimistic view (most of the time, 512 will be enough), and only allocate more space if the first call shows that it is insufficient.
One more downside to the use of a VLA like this is that if you run out of space for the local variable buffer, your code may never get a chance to recover. You must judge how serious a problem that is. If it is an issue, use explicit memory allocation (malloc()) instead:
char buffer = malloc(len+1);
if (buffer == 0)
return; // Report error?
...second vsnprintf() and send()...
free(buffer);
Since your function only ever returned the constant 1, there is no obvious reason to make it a function returning anything - if it is a function returning void, the calling code does not need any check on the returned value. OTOH, maybe you should be returning the result of the send() call (and possibly an error indication if the malloc() fails).
I also made the format (message) parameter into a const char *; neither this function nor the ones it calls modify the format string.
If you're on Linux you could use vasprintf() which will allocate a buffer of the correct size.
If you need portability you can use vsnprintf() to avoid buffer overflows.
I'm trying to build python-kerberos on AIX. kerberospw.c uses a call to asprintf, but from what Google is telling me, asprintf does not exist on AIX.
I saw http://www.koders.com/c/fidAA9B130D588302673A28B568430A83131B7734C0.aspx?s=windows.h, which looks like I could create a stand-in asprintf, but I don't know where this would go or how I would #include it in kerberospw.c.
Is there a way I can use the koders.com example or some other code to "fake" asprintf? Can I just include the asprintf function as shown in kerberospw.c? I am not a C coder, but
asprintf (char **resultp, const char *format, ...)
doesn't look like a valid signature to me with the dots at the end. The relevant line from kerberospw.c is below
asprintf(&message, "%.*s: %.*s",
(int) result_code_string.length,
(char *) result_code_string.data,
(int) result_string.length,
(char *) result_string.data);
I realize I could contact the author of python-kerberos, but a) I think it would be helpful to have a potential patch if I did so, and b) there might be other software I run across that uses asprintf, and it would be nice to have a workaround.
The asprintf is a variation of the printf family of function that allocate a buffer to hold the memory for the formatted string and return it. It is a function with a variable number of argument (hence the ... in the declaration that is valid C code). You can find a description here.
It can be reimplemented relatively easily if the vsnprintf is functioning correctly (ie, return an error if the buffer is too small to hold the formatted string).
Here is such an implementation:
#include <stdarg.h>
int asprintf(char **ret, const char *format, ...)
{
va_list ap;
*ret = NULL; /* Ensure value can be passed to free() */
va_start(ap, format);
int count = vsnprintf(NULL, 0, format, ap);
va_end(ap);
if (count >= 0)
{
char* buffer = malloc(count + 1);
if (buffer == NULL)
return -1;
va_start(ap, format);
count = vsnprintf(buffer, count + 1, format, ap);
va_end(ap);
if (count < 0)
{
free(buffer);
return count;
}
*ret = buffer;
}
return count;
}
Building on Sylvain's answer, here is a simple implementation with both asprintf() and vasprintf() because where you need one, you usually end up needing the other too. And, given the va_copy() macro from C99, it is easy to implement asprintf() in terms of vasprintf(). Indeed, when writing varargs functions, it is very often helpful to have them in pairs, one with the ellipsis notation and one with the va_list argument in place of the ellipsis, and you trivially implement the former in terms of the latter.
This leads to the code:
int vasprintf(char **ret, const char *format, va_list args)
{
va_list copy;
va_copy(copy, args);
/* Make sure it is determinate, despite manuals indicating otherwise */
*ret = NULL;
int count = vsnprintf(NULL, 0, format, args);
if (count >= 0)
{
char *buffer = malloc(count + 1);
if (buffer == NULL)
count = -1;
else if ((count = vsnprintf(buffer, count + 1, format, copy)) < 0)
free(buffer);
else
*ret = buffer;
}
va_end(copy); // Each va_start() or va_copy() needs a va_end()
return count;
}
int asprintf(char **ret, const char *format, ...)
{
va_list args;
va_start(args, format);
int count = vasprintf(ret, format, args);
va_end(args);
return(count);
}
The tricky part of using these functions in a system where they are not provided is deciding where the functions should be declared. Ideally, they'd be in <stdio.h>, but then you wouldn't need to write them. So, you have to have some other header which includes <stdio.h> but declares these functions if they are not declared in <stdio.h>. And, ideally, the code should semi-automatically detect this. Maybe the header is "missing.h", and contains (in part):
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
#include <stdio.h>
#include <stdarg.h>
#ifndef HAVE_ASPRINTF
extern int asprintf(char **ret, const char *format, ...);
extern int vasprintf(char **ret, const char *format, va_list args);
#endif /* HAVE_ASPRINTF */
Also, note that this man page for asprintf() says that the return value in the pointer is indeterminate in case of error. Other man pages, including the one referenced in the question, indicate that it is explicitly set to NULL on error. The C Standard committee document (n1337.pdf) does not specify the error behaviour on lack of memory.
If using asprintf(), do not assume that the pointer is initialized if the function fails.
If implementing asprintf(), ensure that the pointer is set to null on error to give deterministic behaviour.
I came here looking for a quick implementation for Windows and Linux which set the return pointer to NULL on failure.
Jonathan Leffler's answer looked to be the better one, but then I noticed it doesn't set -1 when malloc fails.
I did more searching and came across this discussion of implementing asprintf, which then enlightened me that Jonathan and Sylvain both did not handle overflow correctly either.
I now recommend this solution provided with the aforementioned discussion, which seems to be cover all the important platforms and apparently handles every failure scenario correctly.
Here an implementation that doesn't call snprintf() twice in most of the cases. I omitted the includes and defines as shown in other responses.
As it should be, define the asprintf() as a call to vasprintf()
int asprintf(char **dst, const char * pcFormat, ...)
{
va_list ap;
va_start(ap, pcFormat);
int len = vasprintf(dst, pcFormat, ap);
va_end(ap);
return len;
}
We preallocate a buffer to an predefined appropriate size and only in case of overflow call vsnprintf() a second time. The rationale being that s*printf() function are considered very heavy and overallocating memory being acceptable.
int vasprintf(char **dst, const char * pcFormat, va_list ap)
{
int len = 512; /* Worked quite well on our project */
int allocated = 0;
va_list ap_copy;
char *buff = NULL;
while(len >= allocated) {
free(buff);
buff = malloc(len+1);
if(buff) {
allocated = len+1;
va_copy(ap_copy, ap);
len = vsnprintf(buff, len+1, pcFormat, ap_copy);
va_end(ap_copy);
}
else /* malloc() failed */
return -1;
}
*dst = buff;
return len;
}
EDIT: I replaced the realloc() call by a simple malloc() as it is cheaper. In the case of overflow a free()/malloc() pair costs less than realloc() because of its internal hidden memcpy(). As we overwrite the whole buffer anyway with the subsequent call to vsnprintf() there is no point in that copy.
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?
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");
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");
}
}
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.
Don't use sprintf.
It will overflow your String-Buffer and crash your Program.
Always use snprintf
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.
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
}
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.
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.