passing va_args to a custom format function - c

I'd like to use a va_args list in a custom formatter function.
I first tried to get vsnprintf() to work to verifiy that my argument pointer list itself is passed correctly => this worked
But when i tried to pass my argument pointer list to another function the pointer does not point to the right element on the stack
#include <stdio.h>
#include <stdarg.h>
int wrapper_snprintf(char *s, size_t n, const char *format, ...)
{
va_list arg;
//Copy Formated string to buffer => adds a terminating null character to the string
va_start(arg, format);
int res = vsnprintf(s, n, format, arg);
va_end(arg);
return res;
}
void Va_Args_Test_C(char *fmt, ...)
{
char test[30] = { 0 };
char test2[30] = { 0 };
{
//vsnprintf
va_list arg;
va_start(arg, fmt);
vsnprintf(test2, 30, fmt, arg);
va_end(arg);
}
{
//custom format function
va_list arg;
va_start(arg, fmt);
wrapper_snprintf(test2, 30, fmt, arg);
va_end(arg);
}
}
the first run worked. The correct argument is copied to string test[].
the second run did not work. test2[] contains the original string + some random number (which looks like a crappy pointer to me). So could you help me? What am I doing wrong?

In the first case you call vsnprintf(test2, 30, fmt, arg);.
This function is defined as
int vsnprintf(char *str, size_t size, const char *format, va_list ap);
In the second case you call wrapper_snprintf(test2, 30, fmt, arg);, but your is defined differently as
int wrapper_snprintf(char *s, size_t n, const char *format, ...)
This is as if you would call snprintf instead of vsnprintf.
If you want a replacement for vsnprintf you have to define your function with an argument of type va_list instead of ...
int wrapper_snprintf(char *s, size_t n, const char *format, va_list ap)
{
//Copy Formated string to buffer => adds a terminating null character to the string
int res = vsnprintf(s, n, format, ap);
return res;
}

Related

Formating C string with va_list

Is there a va_list equivalent of snprintf which takes a va_list of variable arguments? I'm trying to implement two functions:
char * __HYP format_cstring(const char * format, ...);
chat * __HYP format_cstringv(const char * format, var_list args);
But I'm not sure how to apply snprintf to this situation. Something like this (notice the question marks):
char * __HYP format_cstring(const char * format, ...)
{
int size = snprintf(NULL, 0, format, ??);
char * buffer = (char *)malloc(size * sizeof(char));
if (snprintf(buffer, size, format, ??) < 0) {
free(buffer);
return NULL;
}
return buffer;
}
And what about its format_cstringv counterpart?
Here's how I ended up doing it:
// .h
char * sformat(const char * format, ...) __attribute__((format (printf, 1, 2)));
char * vsformat(const char * format, va_list args) __attribute__((format (printf, 1, 0)));
And the implementation:
char * __HYP sformat(const char * format, ...)
{
char * buffer;
va_list args;
va_start(args, format);
buffer = __HYP vsformat(format, args);
va_end(args);
return buffer;
}
char * __HYP vsformat(const char * format, va_list args)
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
int size = vsnprintf(NULL, 0, format, args);
if (size <= 0) {
return NULL;
}
char * buffer = new char[size + 1];
if (buffer == NULL) {
return NULL;
}
if (vsnprintf(buffer, static_cast<size_t>(size), format, args) <= 0) {
free(buffer);
return NULL;
}
#pragma GCC diagnostic pop
return buffer;
}
I've been finding out how forgetful I am about C++, after a few years without touching it.
You are interested in the %r format that exists since ~1980 on UNOS and on DECUS.
See: http://austingroupbugs.net/view.php?id=800
There is a 30+ years old implementation in libschily, see: `http://sourceforge.net/projects/schilytools/files/ and a not-yet published implementation as an enhancement to libc on Solaris.
%r permits any printf() based function to be called by a wrapper function that then can offer the full printf() feature set.
%r takes two arguments:
1) a format string
2) a va_list type parameter
It needs an enhancement to the vararg macros called: va_arg_list() similar to and for the same reason why Sun/Solaris had to introduce va_copy() in the early 1990s in order to implement %n$.
BTW: There have been discussions about %r in the usenet around 1985 but the people that have been discussing the feature at that time believed it was not portable. My code has been tested on all existing CPU types and I am interested to have this added to the ISO C standard.
Here is some example code:
int
error(const char *fmt, ...)
{
va_list args;
int ret;
va_start(args, fmt);
ret = js_fprintf(stderr, "%r", fmt, args);
va_end(args);
return (ret);
}

vsnprintf and varargs not working, weird results

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
}

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.

Does a function like this exist? void str_realloc_and_concat(char *str, const char *format, ...)

I'm wondering if such a function exists:
void str_realloc_and_concat(char *str, const char *format, ...)
This function would take a char *str (allocated or NULL), and append to it *format.
I'm looking for something like a sprintf with realocation, strcpy and concatenation.
Does it exist or do I have to code it? Thanks for your inputs.
Update
The library has to be used on a embedded device so I don't want to use GNU extensions, since I'm not sure I'll have them.
Here is an implementation of such a function. To my knowledge the standard C library does not contain a function such as the one you are looking for. Notice that I would recommend to pass the target string as a double pointer, since realloc may change the pointer:
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
const char *prefix = "Hello";
void str_realloc_and_concat(char **str, const char *format, ...)
{
va_list ap;
int i, n;
char *s;
n = strlen(*str);
va_start(ap, format);
i = vasprintf(&s, format, ap);
*str = (char *)(realloc(*str, n + i + 1));
if (*str != NULL)
strncpy(&(*str)[n], s, i);
va_end(ap);
}
int main()
{
char *s = (char *)(malloc(strlen(prefix)));
strncpy(s, prefix, strlen(prefix));
str_realloc_and_concat(&s, " %s", "world!");
printf("%s\n", s);
return 0;
}
I decided to go with this. Hope it will help.
/**
* Reallocate a string and concatenate it with a formatted string.
* The src string has to be allocated by malloc/calloc.
* #param src Pointer to the original string
* #param format Format string
* #param ... Arguments that are to be formatted
*/
char *strcatf(char *src, const char *format, ...)
{
va_list ap, cp;
int format_length;
size_t translation_length;
char *dest;
if (NULL == src) {
return NULL;
}
va_start(ap, format);
va_copy(cp, ap);
translation_length = strlen(src);
format_length = vsnprintf(NULL, 0, format, cp);
if ((dest = realloc(src, (translation_length + format_length + 1) * sizeof(char))) == NULL) {
free(src);
} else {
vsprintf(&(dest)[translation_length], format, ap);
}
va_end(ap);
va_end(cp);
return dest;
}
This does not have the realloc part, but it does allocate. GNU libc's asprintf is like sprintf but allocates the result (to be big enough).
char *res = NULL;
const char *str = "other string";
int ret = asprintf(&res, "Hi %d %s\n", 2, str);
Concatenation is just a special case of using the format string.. if I understand correctly.

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