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.
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
I would like to be able to pass formatted strings to other functions, like my error handler:
error_handler(str_format("error code %d in graphics function: %s", code, error));
My first idea was to use sprintf, but it does not return the result, so you have to add extra lines:
char msg = [100];
sprintf(msg, "error code %d in graphics function: %s", code, error)
error_handler(msg);
And if the error message is bigger than the size of msg then there could be problems.
So I thought I could make a wrapping function like this:
char* str_format (char* format, ...) {
char* output = malloc(100);
va_list args;
va_start(args, format);
vsnprintf(output, 100, format, args);
va_end(args);
return output;
}
But I still have the problem of not knowing the needed size for output. So I thought I could use sizeof to get the size of format and all the args, but there's no way to detect how many args there are unless you manually pass a number of args every time. I could read the format string and look for '%' symbols and detect each placeholder and type, but this seems to be too complicated, just to get the return value of sprintf.
I know there is a function called snprintf that will be safe and limit the number of chars but there is no vsnprintf in c
EDIT: #1 changed output to malloc #2 there is a vsnprintf function after all, so I changed to that. So then the question is down to if there is an easier way than this type of wrapper function. Thank you
Is there an easy way to get sprintf to return the formatted string?
Typically, you use two calls to vsnprintf for this -- the first call with no buffer to get the size needed and a second call to fill the buffer
char* str_format (char* format, ...) {
va_list args;
va_start(args, format);
int len = vsnprintf(0, 0, format, args);
va_end(args);
char *output = malloc(len+1);
va_start(args, format);
vsnprintf(output, len+1, format, args);
va_end(args);
return output;
}
Note that the caller will need to free the allocated pointer (or it will leak)
So I thought I could make a wrapping function like this:
/* !!! DON'T DO THIS !!! */
char* str_format (char* format, ...) {
char output[100];
va_list args;
va_start(args, format);
vsprintf(output, format, args);
va_end(args);
return output;
}
This is bad for multiple reasons: first, as you say you don't know the size of the output. Most importantly however, you cannot possibly return a variable defined locally (like output).
To know the size of the output, you can first make a "dummy" call to vsnprintf with a size of 0, which according to the manual should simply calculate and return the needed size for the output buffer.
As per returning a valid pointer, you could make this work in two different ways:
Allocate a buffer with malloc, then return a pointer to the allocated buffer. This could be annoying since you would need to always free() the allocated buffer, and since you want to use your function like error_handler(str_format(...)) you lose reference to the returned buffer, so you either free() it in error_handler() and always assume that error_handler() will take malloc'd objects, or you need a temporary variable.
Use a static buffer. This however has the problem of needing to be pre-allocated with a constant size at compile time. You could choose this option and limit your error message size. One other limitation of this approach is that only one error at a time can be formatted (i.e. this is not thread safe), as you would be overwriting the same static buffer each time. This is a pretty common among C libraries as it is painless and easy to implement, but only really applicable if you have reasonably-sized error messages.
Option 1 (error checking omitted):
char* str_format(char* format, ...) {
char *buf;
int size;
va_list args;
va_start(args, format);
size = vsnprintf(NULL, 0, format, args) + 1;
va_end(args);
buf = malloc(size);
va_start(args, format);
vsnprintf(NULL, size, format, args);
va_end(args);
return buf;
}
Option 2:
char* str_format(char* format, ...) {
static char buf[1024]; // fixed at compile time
va_list args;
va_start(args, format);
vsnprintf(NULL, 1024, format, args);
va_end(args);
return buf;
}
Note that for vsnprintf you need to define at least one of the following feature test macros, as specified by the manual:
#define _XOPEN_SOURCE 500
// or
#define _ISOC99_SOURCE
// or
#define _BSD_SOURCE // old glibc <= 2.19
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.)
Usually usage of function:
my_func("test");
Whether I can use this parameter so?
my_func(printf("current_count_%d",ad_id));
int my_func(const char *key)
Yes you can use the return value of printf as a function parameter.But remember printf on success returns the number of characters written.So
foo(printf("bar%d",123));
passes 6 to the foo function not bar123.
If you want to pass the string that printf prints, you can make use of sprintf function. It is similar to printf but instead of writing to the console it writes into a char array.
char buf[64]; /* malloc or whatever */
int pos = snprintf(buf, sizeof(buf), "current_count_%d", ad_id);
if (sizeof(buf) <= pos)
buf[sizeof(buf)-1] = '\0';
my_func(buf)
No; printf() returns the number of characters printed to stdout. Use s[n]printf() to create the string, then pass that.
If you're looking to pass a variable number of arguments, like printf accepts, to some function then you need to look into . To reproduce printf (for the sake of argument):
void varargfunc(char *fmt, ...)
{
char formattedString[2048]; /* fixed length, for a simple example - and
snprintf will keep us safe anyway */
va_list arguments;
va_start(arguments, fmt); /* use the parameter before the ellipsis as
a seed */
/* assuming the first argument after fmt is known to be an integer,
you could... */
/*
int firstArgument = va_arg(arguments, int);
fprintf(stderr, "first argument was %d", firstArgument);
*/
/* do an vsnprintf to get the formatted string with the variable
argument list. The v[printf/sprintf/snprintf/etc] functions
differ from [printf/sprintf/snprintf/etc] by taking a va_list
rather than ... - a va_list can't turn back into a ... because
it doesn't inherently know how many additional arguments it
represents (amongst other reasons) */
vsnprintf(formattedString, 2048, fmt, arguments);
/* formattedString now contains the first 2048 characters of the
output string, with correct field formatting and a terminating
NULL, even if the real string would be more than 2047 characters
long. So put that on stdout. puts adds an additional terminating
newline character, so this varies a little from printf in that
regard. */
puts(formattedString);
va_end(arguments); /* clean up */
}
If you wanted to add additional arguments that aren't related to the format, add them before the 'fmt' argument. Fmt is passed to va_start to say "the variable arguments start after this one".
The function printf returns an integer.
int printf ( const char * format, ... );
Hence, you can use it in my_func as long as my_func takes an integer as parameter. This does not seem to be the case.
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.