vsnprintf and varargs not working, weird results - c

I'm using vsnprintf (as far as I know correctly) but am getting weird results. I've simplified my code down to the following example:
void func(char *aaa, ...)
{
char *buf;
va_list args;
int size;
va_start(args, aaa);
size = vsnprintf(NULL, 0, aaa, args)+1;
buf = malloc(size);
vsnprintf(buf, size, aaa, args);
printf("%s",buf);
free(buf);
va_end(args);
}
int main(int argc, char **argv)
{
func("abc %s", "def\n");
return 0;
}
I'd expect "abc def" to get printed, but instead I get "abc" followed by some garbage text. Does anyone have an idea of where I messed up?

va_start(args, aaa);
size = vsnprintf(NULL, 0, aaa, args)+1; // Reads all arguments
buf = malloc(size);
vsnprintf(buf, size, aaa, args); // Tries to read all arguments again
printf("%s",buf);
free(buf);
va_end(args);
See the commented lines. You have to reset args between those two calls consuming all arguments.
Insert:
va_end(args);
va_start(args, aaa);

When you pass the handle args to vsnprintf() it will change it internally in your case. If used again it will not give the correct arguments.
Use va_copy() to create a copy of args if you wish to get your optional arguments twice.
void func(char *aaa, ...)
{
char *buf;
va_list args , argsc ; //argsc is used in the second vsnprintf()
int size;
va_start(args, aaa);
va_copy( argsc , args ) ; //make a copy of args
size = vsnprintf(NULL, 0, aaa, args)+1;
buf = malloc(size);
vsnprintf(buf, size, aaa, argsc); //use the copy since args is not valid
printf("%s",buf);
free(buf);
va_end(args);
va_end(argsc); //destroy both
}

Related

Prepend variadic arguments and pass to another function

I am having some trouble with a variadic printf-style function that I wrote, which works correctly when there are no variadic arguments but not when there are, e.g:
do_print("TestName", ""); works.
do_print("TestName", "Key1:%s\r\nKey2:%d\r\nKey3:%s", "Bob", 4, "Alice"); doesn't
I have done some debugging and I believe the issue is that the variadic arguments themselves are not getting processed as they should be. I looked at some examples and similar questions, and that has led me to believe that I'm misusing va_start and va_end in one of the functions. The format string that I'm generating is correct, but I'm getting a repeat for the first variadic arg of the name itself and then garbage values for the remaining variadic arguments:
Message:
Name:TestName
ID:42
Key1:TestName
Key2:-1
Key3:��-�*
(should be:)
Message:
Name:TestName
ID:42
Key1:Bob
Key2:4
Key3:Alice
My thought was perhaps that I should eliminate a va_start / va_end and just pass the ap in directly, although in certain cases I call the fmt_print function directly so I'm not sure what I would pass in for ap then. That's what I was playing with fmt_print2 for, but right now that just segfaults and is even worse than the original.
And yes, I do need vasprintf. The code here is printf-style, but ultimately I do need the entire message in a single buffer because I then pass it to the write function, not a printf-style function, so fmt_print does need to do the work of combining everything into a single buffer.
Here is a full minimal reproducible example, which I've been testing here: https://www.onlinegdb.com/online_c_compiler
#define _GNU_SOURCE /* needed for vasprintf */
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#pragma GCC diagnostic ignored "-Wsuggest-attribute=format"
static int fmt_print(const char *fmt, ...)
{
int res = 0;
int bytes = 0;
char *buf;
int len;
va_list ap;
va_start(ap, fmt);
if ((len = vasprintf(&buf, fmt, ap)) < 0) {
va_end(ap);
return -1;
}
va_end(ap);
printf("FORMAT STRING: %s\n", fmt);
printf("Message:\n%s[END]", buf);
return res;
}
static int fmt_print2(va_list ap, const char *fmt, ...)
{
int res = 0;
int bytes = 0;
char *buf;
int len;
//va_start(ap, fmt);
if ((len = vasprintf(&buf, fmt, ap)) < 0) {
va_end(ap);
return -1;
}
//va_end(ap);
printf("FORMAT STRING: %s\n", fmt);
printf("Message:\n%s[END]", buf);
return res;
}
#pragma GCC diagnostic pop
static void do_print(const char *name, const char *fmt, ...)
{
va_list ap;
char buf[200];
snprintf(buf, sizeof(buf), "%s%s%s", "Name:%s\r\nID:%d\r\n", fmt, "\r\n\r\n");
// send Name:%s\r\ID:%d\r\nKey1:%s\r\nKey2:%d\r\nKey3:%s fmt string + all args.
//fmt_print2(ap, buf, name, 42);
va_start(ap, fmt);
fmt_print(buf, name, 42);
va_end(ap);
}
int main()
{
do_print("TestName", "Key1:%s\r\nKey2:%d\r\nKey3:%s", "Bob", 4, "Alice");
// This is also valid:
fmt_print("Login", "Name:%s\r\nID:%d\r\nUsername:%s\r\nPassword:%s", "Login", 41, username, password);
return 0;
}
Could someone point me in the right direction of what approach is best suited for this?
As I said before in a comment, you can't do what you are trying to do the way you are trying to do it. The snprintf() in do_print() is trying to set a format string that requires two separate va_list values to process it. You will have to keep the two lots of formatting separate. And then, if necessary, combine the two strings.
This code works. It contains a lot of diagnostic printing which you can remove, but it showed me where things were going wrong. It includes the trailing \r\n\r\n as a separate operation.
/* SO 7274-3429 */
#define _GNU_SOURCE /* needed for vasprintf */
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int fmt_print(const char *fmt1, va_list ap1, const char *fmt2, ...)
{
char *buf1;
char *buf2;
int len1;
int len2;
va_list ap2;
char *buffer;
printf("%s(): fmt1 = [[%s]]\n", __func__, fmt1);
len1 = vasprintf(&buf1, fmt1, ap1);
printf("%s(): buf1 = [[%s]]\n", __func__, buf1);
printf("%s(): fmt2 = [[%s]]\n", __func__, fmt2);
va_start(ap2, fmt2);
len2 = vasprintf(&buf2, fmt2, ap2);
va_end(ap2);
printf("%s(): buf2 = [[%s]]\n", __func__, buf2);
if (len1 < 0 || len2 < 0)
return -1;
printf("%s(): Format strings:\n[[%s]]\n[[%s]]\n", __func__, fmt1, fmt2);
printf("%s(): Message strings:\n[[%s]]\n[[%s]]\n[END]\n", __func__, buf1, buf2);
buffer = malloc(len1 + len2 + sizeof("\r\n\r\n"));
if (buffer == 0)
{
free(buf1);
free(buf2);
return -1;
}
strcpy(buffer, buf2);
strcpy(buffer + len2, buf1);
strcpy(buffer + len2 + len1, "\r\n\r\n");
printf("Full buffer: [[%s]]\n", buffer);
free(buf1);
free(buf2);
free(buffer);
return 0;
}
static void do_print(const char *name, const char *fmt, ...)
{
va_list ap;
char buf[] = "Name:%s\r\nID:%d\r\n";
va_start(ap, fmt);
fmt_print(fmt, ap, buf, name, 42);
va_end(ap);
}
int main(void)
{
do_print("TestName", "Key1:%s\r\nKey2:%d\r\nKey3:%s", "Bob", 4, "Alice");
return 0;
}
The output from it is:
fmt_print(): fmt1 = [[Key1:%s
Key2:%d
Key3:%s]]
fmt_print(): buf1 = [[Key1:Bob
Key2:4
Key3:Alice]]
fmt_print(): fmt2 = [[Name:%s
ID:%d
]]
fmt_print(): buf2 = [[Name:TestName
ID:42
]]
fmt_print(): Format strings:
[[Key1:%s
Key2:%d
Key3:%s]]
[[Name:%s
ID:%d
]]
fmt_print(): Message strings:
[[Key1:Bob
Key2:4
Key3:Alice]]
[[Name:TestName
ID:42
]]
[END]
Full buffer: [[Name:TestName
ID:42
Key1:Bob
Key2:4
Key3:Alice
]]
Note that this code frees the memory allocated by vasprintf().
Since you try to compile with -Wmissing-format-attribute, you will still get errors from a verbatim copy of this code. It will compile cleanly with your options if you add:
#if !defined(PRINTFLIKE)
#if defined(__GNUC__)
#define PRINTFLIKE(n, m) __attribute__((format(printf, n, m)))
#else
#define PRINTFLIKE(n, m) /* If only */
#endif /* __GNUC__ */
#endif /* PRINTFLIKE */
and change the function definition lines to:
static int PRINTFLIKE(3, 4) fmt_print(const char *fmt1, va_list ap1, const char *fmt2, ...)
and
static void PRINTFLIKE(2, 3) do_print(const char *name, const char *fmt, ...)
and in do_print(), modify the call to fmt_print() to take a literal format (eliminating the variable buf):
fmt_print(fmt, ap, "Name:%s\r\nID:%d\r\n", name, 42);
A lot of the time, the 'non-literal format' check is helpful and sensible, but there are times when it is counter-productive.
You simply forgot to allocate memory for your fmt_print function.
static int fmt_print2(const char *fmt, va_list ap);
static int fmt_print(const char *fmt, ...)
{
int res = 0;
int bytes = 0;
char *buf;
int len;
va_list ap;
va_start(ap, fmt);
if ((len = vsnprintf(NULL, 0, fmt, ap)) < 0) {
va_end(ap);
return -1;
}
buf = malloc(len + 1);
printf("LEN = %d\n", len);
va_start(ap, fmt);
if(buf)
{
vsnprintf(buf, len, fmt, ap);
printf("FORMAT STRING: %s\n", fmt);
printf("Message:\n%s[END]", buf);
}
va_end(ap);
return res;
}
static int fmt_print1(const char *fmt, ...)
{
int res = 0;
int bytes = 0;
char *buf;
int len;
va_list ap;
va_start(ap, fmt);
fmt_print2(fmt, ap);
va_end(ap);
return res;
}
static int fmt_print2(const char *fmt, va_list ap)
{
int res = 0;
int bytes = 0;
char *buf;
int len;
va_list copy;
va_copy(copy, ap);
if ((len = vsnprintf(NULL, 0, fmt, ap)) < 0) {
va_end(ap);
return -1;
}
buf = malloc(len + 1);
if(buf)
{
vsnprintf(buf, len, fmt, copy);
printf("FORMAT STRING: %s\n", fmt);
printf("Message:\n%s[END]", buf);
}
free(buf);
return res;
}
int main()
{
fmt_print("Key1:%s\r\nKey2:%d\r\nKey3:%s", "Bob", 4, "Alice");
fmt_print("Key1:%s\r\nKey2:%d\r\nKey3:%s", "AAAA", 8, "BBBB");
}

Is there a way to bypass va_copy when using vsnprintf?

I'm introducing a 3rd party protocol stack to on an old embeded platform where all va_* stuff are implemented except va_copy. The problem I face is, in the 3rd party stack, vsnprintf() is used:
int fun(char **buf, size_t buf_size, const char *fmt, va_list ap) {
va_list ap_copy;
int len;
/* first call*/
va_copy(ap_copy, ap);
len = vsnprintf(*buf, buf_size, fmt, ap_copy);
va_end(ap_copy);
if(len >= buf_size)
{
/* 2nd call*/
va_copy(ap_copy, ap);
len = vsnprintf(*buf, len + 1, fmt, ap_copy);
va_end(ap_copy);
}
}
Luckily the 3rd party stack provedes its own vsnprintf function(call it new_vsnprintf), but without va_copy, only the first call works, i.e, when the len is small than buf_size. Below is the way I call it:
#define vsnprintf new_vsnprintf
int fun(char **buf, size_t buf_size, const char *fmt, va_list ap) {
//va_list ap_copy;
int len;
/* first call*/
va_start(ap, fmt);
len = vsnprintf(*buf, buf_size, fmt, ap);
va_end(ap);
if(len >= buf_size)
{
/* 2nd call*/
va_start(ap, fmt);
len = vsnprintf(*buf, len + 1, fmt, ap); //new_vsnprintf()
va_end(ap);
}
}
Problem occurs in the 2nd call of new_vsnprintf(), when trying to get the actual value of placeholders by va_arg(). I assume the inner pointer of (va_list) ap points to wrong memory address.
Then how to correct it?
You shouldn't be calling either va_start or va_end in your function fun. They should only be called in functions that have ... in the argument list to indicate that there are a variable number of arguments. My version of gcc (4.8.4) even throws up an error if I try and compile your code as it currently exists.
test.c: In function ‘fun’:
test.c:14:3: error: ‘va_start’ used in function with fixed args
va_start(ap, fmt);
It seems likely that calling them in functions with fixed args is causing undefined behaviour and it's luck that the first call to vsnprintf is actually working.
Since you're not calling va_arg inside fun the value of ap shouldn't be changing and you should be able to call vsnprintf as many times as you require.
This answer may work if your platform has a simple varargs handling, like most platform do. If your platform do not permit va_list object to be trivially copied this cannot work. On most platforms va_copy is a simple macro that does some thing like:
#define va_copy(dest, src) dest = src
... this is the case for x86, ppc and many other targets. Anyway here is the code, I hope it works, I've no way to test or confirm it works without knowing the platform you are using it on!
int fun(char **buf, size_t buf_size, const char *fmt, va_list ap) {
va_list ap_copy = ap;
int len;
/* first call*/
va_start(ap_copy, fmt);
len = vsnprintf(*buf, buf_size, fmt, ap_copy);
va_end(ap_copy);
if(len >= buf_size)
{
/* 2nd call*/
va_start(ap, fmt);
len = vsnprintf(*buf, len + 1, fmt, ap); //new_vsnprintf()
va_end(ap);
}
}

Create a "template" (format) string

I want to be able to create a template string and then use it like this:
int execute_command(char *cmd) {
//...
}
char *template_command = "some_command %s some_args %s %d";
char *actual_command = template_command % (cmd1, arg1, 123);
// error, how do I do that?
int res = execute_command(actual_command);
If you know the maximum length of actual_command, then you can use either one of the following:
char actual_command[MAX_LEN+1];
// Option #1
sprintf(actual_command, template_command, cmd1, arg1, 123);
// Option #2
snprintf(actual_command, MAX_LEN+1, template_command, cmd1, arg1, 123);
If MAX_LEN is not defined correctly, then:
Option #1 will result with undefined behavior (possibly runtime exception)
Option #2 will result with incorrect result (contents of actual_command)
Use snprintf and malloc (snprint returns the length of the string it would have written was the buffer only big enough, and receives the size of the buffer). POSIX asnprintf packages that nicely.
If you don't have it, define your own like this:
char* my_asnprintf(const char* format, ...) {
va_list arg;
va_start(arg, format);
size_t n = 1 + vsnprintf((char*)format, 0, format, arg);
va_end(arg);
char* ret = malloc(n);
if(!ret)
return ret;
va_start(arg, format);
vsnprintf(ret, n, format, arg);
va_end(arg);
return ret;
}
Don't forget to free the buffer.

How to remove this warning: second parameter of ‘va_start’ not last named argument?

I have a function (see below) that is emitting the following warning:
second parameter of ‘va_start’ not last named argument
What does it means and how to remove it?
The function is as the following:
static int ui_show_warning(GtkWindow *parent, const gchar *fmt, size_t size, ...)
{
GtkWidget *dialog = NULL;
va_list args = NULL;
int count = -1;
char *msg = NULL;
if((msg = malloc(size + 1)) == NULL)
return -12;
va_start(args, fmt);
if((count = snprintf(msg, size, fmt, args)) < 0)
goto outer;
dialog = gtk_message_dialog_new(parent,
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_WARNING,
GTK_BUTTONS_OK,
"%s", msg);
(void) gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
outer: {
if(args != NULL)
va_end(args);
if(msg != NULL)
free(msg);
return count;
}
}
You need to use size instead of fmt:
va_start(args, size);
It is size, not fmt, that is the last parameter that has an explicit name (as opposed to vararg parameters, which have no names). You need to pass the last named parameter to va_start in order for it to figure out the address in memory at which the vararg parameters start.
second parameter of ‘va_start’ not last named argument
What does it means and how to remove it?
Your function has named parameters parent, fmt and size. The C spec says you have to always pass the last named parameter to va_start, for compatibility with older compilers. So you must pass size, not fmt.
(But with a modern compiler, it might work anyway)
I think there is a confusion here: most of people only deal with prinf-like functionsh which have format and varargs. and they think they have to pass parameter name which describes format. however va_start has nothing to do with any kind of printf like format. this is just a function which calculates offset on the stack where unnamed parameters start.
I have the same problem on Ubuntu20.04,Contrary to the answer with the most likes,
the code at the beginning,
void sprintf(char *str, char *fmt, ...) {
va_list list;
int i, len;
va_start(list, 2);
...
}
and then, the code as follows
void sprintf(char *str, char *fmt, ...) {
va_list list;
int i, len;
va_start(list, fmt);
...
}
Problem was sovled.

C, Dealing with variable argument functions

Let's say I want to do something like this
void my_printf(char *fmt,...) {
char buf[big enough];
sprintf(buf,fmt,...);
}
What is the proper way of passing the variable number of arguments directly to a function with accepts variable arguments?
sprintf has a va_list form called vsprintf. Pass the va_list you construct locally to it as the last argument.
void my_printf(char *fmt,...) {
va_list ap;
va_start(ap, fmt);
char buf[big enough];
vsprintf(buf,fmt,ap);
va_end(ap);
}
I'm not sure how useful this code will be, as it is C++, but it shows how to check, using a Win32 specific function vsnprintf(), that the buffer allocated is big enough and if not allocates a bigger one. And it returns a std::string, so you would have to use malloc/realloc to handle that. But what the hell:
string Format( const char * fmt, ... ) {
const int BUFSIZE = 1024;
int size = BUFSIZE, rv = -1;
vector <char> buf( size );
do {
va_list valist;
va_start(valist, fmt );
// if vsnprintf() returns < 0, the buffer wasn't big enough
// so increase buffer size and try again
rv = _vsnprintf( &buf[0], size, fmt, valist );
va_end( valist );
size *= 2;
buf.resize( size );
}
while( rv < 0 );
return string( &buf[0] );
}
You can us the vsprintf style functions to get printf style printing for your variable length parameter. However there is no requrement to do so. You can if you choose write your function to keep accepting parameters until it encounters a null pointer.
va_list ap;
char *param;
va_start(ap,fmt);
param = va_arg(ap,char*);
while(param)
{
do something...
param = va_arg(ap,char*);
}
or you can have the number of parameters as the first param to your function
void my_printf(int param_num,...)
{
va_list ap;
char *param;
va_start(ap,fmt);
while(param_num)
{
do something...
param = va_arg(ap,char*);
param_num--;
}
}
Its really up to you, the possibilities are limitless. I think the only real requirement to the ellipses is that it has at least one parameter before the ellipses.

Resources