Creating C formatted strings (not printing them)
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?
Solution 1:
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");
Solution 2:
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()
andsnprintf()
to produce correct behavior in all cases, you end up with roughly twice as much code than I have given forasprintf()
, and basically reprogram the functionality ofasprintf()
.
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");
}
}