I have some code that converts variadic parameters into a va_list
, then passes the list on to a function that then calls vsnprintf
. This works fine on Windows and OS X, but it is failing with odd results on Linux.
In the following code sample:
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
char *myPrintfInner(const char *message, va_list params)
{
va_list *original = ¶ms;
size_t length = vsnprintf(NULL, 0, message, *original);
char *final = (char *) malloc((length + 1) * sizeof(char));
int result = vsnprintf(final, length + 1, message, params);
printf("vsnprintf result: %d\r\n", result);
printf("%s\r\n", final);
return final;
}
char *myPrintf(const char *message, ...)
{
va_list va_args;
va_start(va_args, message);
size_t length = vsnprintf(NULL, 0, message, va_args);
char *final = (char *) malloc((length + 1) * sizeof(char));
int result = vsnprintf(final, length + 1, message, va_args);
printf("vsnprintf result: %d\r\n", result);
printf("%s\r\n", final);
va_end(va_args);
return final;
}
int main(int argc, char **argv)
{
char *test = myPrintf("This is a %s.", "test");
char *actual = "This is a test.";
int result = strcmp(test, actual);
if (result != 0)
{
printf("%d: Test failure!\r\n", result);
}
else
{
printf("Test succeeded.\r\n");
}
return 0;
}
The output of second vsnprintf
call is 17, and the result of strcmp
is 31; but I don't get why vsnprintf
would return 17 seeing as This is a test.
is 15 characters, add the NULL
and you get 16.
Related threads that I've seen but do not address the topic:
With @Mat's answer (I am reusing the va_list
object, which is not allowed), this comes squarely around to the first related thread I linked to. So I attempted this code instead:
char *myPrintfInner(const char *message, va_list params)
{
va_list *original = ¶ms;
size_t length = vsnprintf(NULL, 0, message, params);
char *final = (char *) malloc((length + 1) * sizeof(char));
int result = vsnprintf(final, length + 1, message, *original);
printf("vsnprintf result: %d\r\n", result);
printf("%s\r\n", final);
return final;
}
Which, per the C99 spec (footnote in Section 7.15), should work:
It is permitted to create a pointer to a va_list and pass that pointer to another function, in which case the original function may make further use of the original list after the other function returns.
But my compiler (gcc 4.4.5 in C99 mode) gives me this error regarding the first line of myPrintfInner
:
test.c: In function ‘myPrintfInner’:
test.c:8: warning: initialization from incompatible pointer type
And the resulting binary produces the exact same effect as the first time around.
Found this: Is GCC mishandling a pointer to a va_list passed to a function?
The suggested workaround (which wasn't guaranteed to work, but did in practice) is to use arg_copy
first:
char *myPrintfInner(const char *message, va_list params)
{
va_list args_copy;
va_copy(args_copy, params);
size_t length = vsnprintf(NULL, 0, message, params);
char *final = (char *) malloc((length + 1) * sizeof(char));
int result = vsnprintf(final, length + 1, message, args_copy);
printf("vsnprintf result: %d\r\n", result);
printf("%s\r\n", final);
return final;
}
As Mat notes, the problem is that you're reusing the va_list
. If you don't want to restructure your code as he suggests, you can use the C99 va_copy()
macro, like this:
char *myPrintfInner(const char *message, va_list params)
{
va_list copy;
va_copy(copy, params);
size_t length = vsnprintf(NULL, 0, message, copy);
va_end(copy);
char *final = (char *) malloc((length + 1) * sizeof(char));
int result = vsnprintf(final, length + 1, message, params);
printf("vsnprintf result: %d\r\n", result);
printf("%s\r\n", final);
return final;
}
On compilers that don't support C99, you may be able use __va_copy()
instead or define your own va_copy()
implementation (which will be non-portable, but you can always use compiler / platform sniffing in a header file if you really need to). But really, it's been 13 years — any decent compiler should support C99 these days, at least if you give it the right options (-std=c99
for GCC).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With