I have a function which implements a custom variant of printf.
Does anyone have a clue how I can print out one more parameter with va_list? My format is like "%d some text %s". I need a, to be printed as first parameter.
void func (int a, char *fmt, ...) {
va_list ap;
va_start (ap, fmt);
// vprintf(stdout, fmt, a, ap) //Can't do like this :(
vfprintf(fmt, a, ap); //Or like this :(
va_end(ap);
}
If a is always printed the same way, it shouldn't be part of the format passed in. You should instead print it separately.
In the example you gave, change your format string to "some text %s". Then your function can do this:
void func (int a, char *fmt, ...) {
va_list ap;
// first print the function specific fields
printf("%d ", a);
// then the user's format
va_start (ap, fmt);
vprintf(fmt, ap);
va_end(ap);
}
As noted in comments:
Redesign the interface
You would do best to redesign the interface so that the format string only applies to the ... (va_list) arguments; the fixed argument gets treated separately.
Standard reasons for wanting extra arguments are logging things like __FILE__ and __LINE__ (presumably not __func__ if you don't have C99 __VA_ARGS__ support — see below). You would probably do best to remove the option of caller-controlled formatting of those fixed arguments, leaving the fmt to process just the ... arguments.
If you can't redesign the interface
The fact that the %d is part of the format and the int value is not part of the va_list makes life tricky.
If you cannot, or decline to, change the rules for calling the function, you'll need to parse the format, for example separating it into the part up to (but not including) the second %, and use that format with the standalone argument; the remainder of the format is then passed to vprintf(). If you're on a POSIX system and in a multi-threaded program, you might want to use flockfile() and funlockfile() on stdout to ensure that they're treated as a single unit of output despite the multi-threading.
// Using C99 features
void func(int a, char *fmt, ...)
{
char *p1 = strchr(fmt, '%');
if (p1 == 0)
{
// No conversion specifications. Not puts(); it adds a newline
fputs(fmt, stdout);
return;
}
char *p2 = strchr(p1 + 1, '%');
if (p2 == 0)
{
// The user invoked func(1, "iteration %d:\n");?
printf(fmt, a);
return;
}
int buflen = p2 - fmt + 1;
char buffer[buflen];
memmove(buffer, fmt, buflen);
buffer[buflen-1] = '\0';
// flockfile(stdout); // Multi-threading
printf(buffer, a);
va_list ap;
va_start(ap, fmt);
vprintf(p2, ap);
va_end(ap);
// funlockfile(stdout); // Multi-threading
}
The code cheats; it uses a C99 VLA (variable length array) and also interleaved declarations and statements. In C90, therefore, you'd probably end up using malloc(), or perhaps a fixed size buffer (running risks of overflows), or a hybrid (use a fixed size buffer if the prefix of the format string is small enough to fit, or allocate (and free) if not).
You need to decide on the appropriate error handling strategies. Note that this isn't bullet-proof code. Calling func(a, "%% yield is %d; data is [%s]\n", data); throws another spanner in the works. Also, if you're on POSIX and someone passes "it is %2$s and %1$d things happened", you're in deep trouble.
Yes, I'm on POSIX system. But it is quite old, I am even not able to use approaches with __VA_ARGS__ macros as it appears in C99+ standards.
It's curious that you're on such an old system. However, that simply means you probably won't write code with the n$ notations in it, so it is one less thing to worry about. The multi-threading issue is less likely to be a problem either. And you can't use the other C99 features that were shown in my sample code.
You could look at the FFI (foreign function interface) library; I'm not at all sure it will help, though.
Based on previous answer to parse format, I've found a simple solution (if %d is always first):
void func (int a, char *fmt, ...) {
char *new_format = malloc(10*sizeof(char));
snprintf(new_format, 10, "%d", a);
strcat(new_format, (fmt+2));
va_list ap;
va_start (ap, fmt);
vprintf(stdout, new_format, ap);
va_end(ap);
}
Special Thanks to Jonathan Leffler
Related
Using the <stdarg.h> header, one can make a function that has a variable number of arguments, but:
To start using a va_list, you need to use a va_start macro that needs to know how many arguments there, but the printf & ... that are using va_list don't need the argument count. How can I create a function that doesn't need the argument count like printf?
Let's say I want to create a function that takes a va_list and instead of using it, passes it to another function that requires a va_list? (so in pseudocode, it would be like void printfRipOff(const char* format, ...) {printf(format, ...);})
Let's say I want to create a function that takes a va_list and instead of using it, passes it to another function that requires a va_list?
Look at function vprintf( const char * format, va_list arg ); for one example of a function that takes a va_list as an input parameter.
It is basically used this way:
#include <stdio.h>
#include <stdarg.h>
void CountingPrintf(const char* format, ...)
{
static int counter = 1;
printf("Line # %d: ", counter++); // Print a Counter up front
va_list args;
va_start(args, format); // Get the args
vprintf(format, args); // Pass the "args" through to another printf function.
va_end(args);
}
int main(void) {
CountingPrintf("%s %s\n", "Hello", "World");
CountingPrintf("%d + %d == %d\n", 2, 3, 2+3);
return 0;
}
Output:
Line # 1: Hello World
Line # 2: 2 + 3 == 5
Functions like printf() and scanf() have the format string argument that tells them the number and types of the arguments that must have been provided by the function call.
If you're writing your own function, you must have some way of knowing how many arguments were provided and their types. It may be that they are all the same type. It may be that they're all pointers and you can use a null pointer to indicate the end of the arguments. Otherwise, you probably have to include a count.
You can do that as long as provided the called function expects a va_list. There are caveats (see C11 §7.16 Variable arguments) but they're manageable with a little effort.
A very common idiom is that you have a function int sometask(const char *fmt, ...) and a second function int vsometask(const char *fmt, va_list args), and you implement the first as a simple call to the second:
int sometask(const char *fmt, ...)
{
va_list args;
va_start(fmt, args);
int rc = vsometask(fmt, args);
va_end(args);
return rc;
}
The second function does the real work, of course, or calls on other functions to do the real work.
In the question, you say:
… va_start macro that needs to know how many arguments …
No; the va_start macro only needs to know which argument is the one before the ellipsis — the argument name before the , ... in the function definition.
In a comment, you say:
This is what I'm trying to write, but it didn't work.
string format(const char* text, ...)
{
// temporary string used for formatting
string formattedString;
initializeString(&formattedString, text);
// start the va_list
va_list args;
va_start(text, args);
// format
sprintf(formattedString.array, text, args);
// end the va_list
va_end(args);
return formattedString;
}
As noted by abelenky in a comment, you need to use vsprintf():
string format(const char* text, ...)
{
string formattedString;
initializeString(&formattedString, text);
va_list args;
va_start(text, args);
vsprintf(formattedString.array, text, args);
va_end(args);
return formattedString;
}
That assumes that the formattedString has enough space in the array for the formatted result. It isn't immediately obvious how that's organized, but presumably you know how it works and it works safely. Consider using vsnprintf() if you can determine the space available in the formattedString.
The printf and scanf family of functions don't need to be explicitly passed the length of the va_list because they are able to infer the number of arguments by the format string.
For instance, in a call like:
printf("%d %c %s\n", num, c, "Hello");
printf is able to examine the first parameter, the format string, and see that it contains a %d, a %c, and a %s in that order, so it can assume that there are three additional arguments, the first of which is a signed int, the second is a char and the third is a char* that it may assume is a pointer to a null-terminated string.
To write a similar function that does not need to be explicitly told how many arguments are being passed, you must find a way to subtly give it some information allowing it to infer the number and types of the arguments in va_list.
I want to use printf for some basic debugging (Yes, I am familiar with gdb, etc.). As printf buffers, a call to fflush(stdout) is required immediately after a printf. I thought of writing a simple wrapper. So the wrapper will be something like this:
flushedPrintf(int noArgs, args to be passed to printf);
I think that the implementation of such a function would be something like this:
void flushedPrintf(int noArgs, args to be passed to printf) {
char *myString = malloc(MAX_LENGTH_DEFINED);
sprintf(myString, ....args to be passed to printf);
printf("%s", myString);
fflush(stdout);
}
I know there are functions/macros like va_list, va_start and va_args but those will require me to parse the printf format string to get the type and I want to avoid doing that. Is there a simple approach? As this is only for debugging purpose, I do not mind having the format type restricted to %d, %f and %s.
Just use setvbuf and set the stream to be line buffered.
setvbuf(stdout, NULL, _IOLBL, 0);
Is there a simple approach?
Sure, just forward the arguments.
void flushedPrintf(const char *fmt, ...) {
va_list va;
va_start(va, fmt);
vprintf(fmt, va);
va_end(va);
fflush(stdout);
// TODO: propagate return value from vprintf and fflush
}
All standard *printf functions have v*printf alternatives that accept a va_list argument. If you didn't know about them here is a nice reference on cppreference.
here are functions/macros like va_list, va_start and va_args but those will require me to parse the printf format string to get the type
That's wrong - using va_* macros do not require you to parse printf format string.
Use stderr and fprintf. Stderr is unbuffered by default.
This question is answered in better detail here:
Why does printf not flush after the call unless a newline is in the format string?
As an extension (and maybe some correction) to the #KamilCuk answer.
If you use gcc family compiler you can use the special function attribute showing the compiler that the function is printf-like. The compiler will analyse the format string and emit the warnings - same as when using printf.
int flushedPrintf(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
int flushedPrintf(const char *fmt, ...)
{
int result;
va_list va;
va_start(va, fmt);
result = vprintf(fmt, va);
va_end(va);
fflush(stdout);
return result;
}
https://godbolt.org/z/SbGRxk
This has been asked in several different flavors. Yet I can still not get it to work. Here is my function definition.
void
ncurses_add_line(const char *fmt, ...)
{
if (ncurses_window) {
va_list args;
va_start(args, fmt);
printw(fmt, args);
printw("\n");
va_end(args);
}
}
When I call this function I get gibberish in the variadic print out of my function. If I call printw directly it all works. For example, if I call ncurses_add_line like ncurses_add_line("Hello %d", var) I get a value not store in var. However, if I call printw("Hello %d", var), I see the value of var displayed next to "Hello" as in, if var == 1 then "Hello 1" is printed with printw but this is not the case for ncurses_add_line.
What do I need to change?
My reason for wrapping this up is because I don't want to include in my header file, only in my c file.
Try vwprintw instead of printw. vwprintw takes a va_list as its argument.
The idiom you're trying to use -- passing a va_list to a function that takes a variable number of arguments -- won't work. One solution is to find a variant of the function that will work (in this case, vwprintw). An alternative is to "flatten" the va_list: in this case, you could use vsprintf to create a formatted string, and then pass that into curses.
args is not something like an array of arguments. It is an internal structure. You have to read out every single argument by passing the type. Please keep in mind, that in C there is no runtime reflection, so you have to add the types in your code.
void ncurses_add_line(const char *fmt, ...)
{
if (ncurses_window)
{
va_list args;
va_start(args, fmt);
char *arg = va_arg( args, int ); // take out one arg by giving the type (int)
printw(fmt, arg);
printw("\n");
va_end(args);
}
}
I want to do some exercise about va_list. This is my code.
int myscanf( char* fmt, ... ) {
va_list ap;
va_start ( ap, fmt );
vfscanf ( stdin, fmt, ap );
va_end ( ap );
}
int main() {
int a, b;
myscanf( "%d %d", &a, &b );
}
As I shown above, I have written a scanf() and it is work.
Now I want to redirect the value of arguments in myscanf().
For example, I can redirect fmt to the space which is allocated in myscanf()
int myscanf( char* fmt, ... ) {
char newFmt[10] = "%d %d";
va_list ap;
va_start ( ap, fmt );
vfscanf ( stdin, newFmt, ap );
va_end ( ap );
}
However, I feel confused when I try to change the value of others arguments.
I can fetch these variable argument by va_arg(), but I can't modify them because va_arg() is a macro.
int myscanf( char* fmt, ... ) {
va_list ap;
va_start ( ap, fmt );
int* arg1 = (int)va_arg(ap, int*); // get the value of &a in main()
int newA; // I want to read a value by scanf() and store it to &newA
// ??? = &newA // <- how to do?
vfscanf ( stdin, fmt, ap );
va_end ( ap );
}
Any suggestion?
-----------edit-----------
Thanks for replies,
But something should be clarified.
The "value" in this case is "address". Therefore, my purpose is changing the target address so that the vfscanf() will read and write the value to the another address space.
For example,
int gA, gB, gC, gD;
int myscanf( char* fmt, ... ) {
va_list ap;
va_start ( ap, fmt );
// do something for making the following vfscanf() to write value into gC and gD directly
vfscanf ( stdin, fmt, ap );
// don't assign *gA to *gC and *gB to *gD after performing vfscanf()
va_end ( ap );
}
int main() {
myscanf( "%d %d", &gA, &gB );
}
As I change fmt to newFmt, we want to change the value (in this case is address) in va_list directly.
And the parsing problem is solved because that I can allocate a space dynamically while I parse a "%..." from format string. These addresses of spaces will replace inputs repeatedly if the question above is solved.
Variadic Functions
The arguments to scanf will always be pointers, not values as in your example. The correct way of getting an argument of scanf would be int *arg1 = va_arg(ap, int*); - and you don't need to cast.
If you want to manipulate the way scanf behaves, you have to know first how variadic functions work (you can get it by reading the manual of any of the va_* family of functions). The variable ap in most architectures is a pointer to the function's stack frame. It points to the next variable after fmt in this case.
Your example
In the case of scanf in your example, it will point to a list of pointers (because all arguments to scanf must be pointers). So you should put that into your pointers like this:
int *a = va_arg(ap, int*);
/* Then you can modify it like this: */
*a = 666;
There are some problems with this.
When you finish manipulating the arguments, you must pass fmt and ap to vfscanf, which will then parse fmt and expect n elements (the amount of elements in the format string). The problem is that ap now will only give us n - x elements (x being the number of elements you "poped" in your own function). A little example:
myscanf("%d %d", &a, &b);
/* n = 2 */
...
int *a = va_arg(ap, int *);
/* x = 1 */
...
vfscanf(stdin, fmt, ap);
/* n = 2 cause fmt is still the same, however
* x = 1, so the number of elements "popable" from the stack is only
* n - x = 2 - 1 = 1.
*/
In this simple example you can already see the problem. vfscanf will call va_arg for each element it finds in the format string, which is n, but only n - x are popable. This means undefined behavior - vfscanf will be writing somewhere it shouldn't and most probably will crash your program.
Hack Around
To overcome that, I propose a little work with va_copy. The signature of va_copy is:
void va_copy(va_list dest, va_list src);
And something to know about it (from the manual):
Each invocation of va_copy() must be matched by a corresponding invocation of va_end() in the same function. Some systems that do not supply va_copy() have __va_copy instead, since that was the name used in the draft proposal.
The solution:
#include <stdio.h>
#include <stdarg.h>
int myscanf(char *fmt, ...)
{
va_list ap, hack;
/* start our reference point as usual */
va_start(ap, fmt);
/* make a copy of it */
va_copy(hack, ap);
/* get the addresses for the variables we wanna hack */
int *a = va_arg(hack, int*);
int *b = va_arg(hack, int*);
/* pass vfscanf the _original_ ap reference */
vfscanf(stdin, fmt, ap);
va_end(ap);
va_end(hack);
/* hack the elements */
*a = 666;
*b = 999;
}
int main(void)
{
int a, b;
printf("Type two values: ");
myscanf("%d %d", &a, &b);
printf("Values: %d %d\n", a, b);
return 0;
}
Conclusion and Warnings
There are a couple of things you should note. First, if you put the hacking of the elements before calling vfscanf, the values you set will be lost, because vfscanf will overwrite those locations.
Next, you should also note that this is a very specific use case. I knew beforehand that I was going to pass two integers as arguments, so I designed myscanf with that in mind. But this means you need a parsing pass to find out which arguments are of which type - if you don't do it, you'll enter undefined behavior again. Writing that kind of parser is very straight-forward and shouldn't be a problem.
After your edit
After what you said in your clarification edit, I can only propose a little wrapper function around vfscanf(), because you can't directly manipulate va_list variables. You can't write directly to the stack (in theory, you can't, but if you did some inline-assembly you could, but that's gonna be an ugly hack and very non-portable).
The reason it's gonna be extremely ugly and non-portable is that the inline assembly will have to take into account how the architecture treats argument passing. Writing inline-assembly by itself is already very ugly... Check out this for the official GCC manual on it's inline assembly.
Back to your problem:
Stack Overflow: How do I fill a va_list
That answer explains a whole lot, so I won't say it here again. The final conclusion of the answer is **no, you don't do it". What you _can do however, is a wrapper. Like this:
#include <stdio.h>
#include <stdarg.h>
int a, b, c, d;
void ms_wrapper(char *newfmt, ...)
{
va_list ap;
va_start(ap, newfmt);
vfscanf(stdin, newfmt, ap);
va_end(ap);
}
int myscanf(char *fmt, ...)
{
/* manipulate fmt.... */
char *newfmt = "%d %d";
/* come up with a way of building the argument list */
/* call the wrapper */
ms_wrapper(newfmt, &c, &d);
}
int main(void)
{
a = 111;
b = 222;
c = 000;
d = 000;
printf("Values a b: %d %d\n", a, b);
printf("Values c d: %d %d\n\n", c, c);
printf("Type two values: ");
myscanf("%d %d", &a, &b);
printf("\nValues a b: %d %d\n", a, b);
printf("Values c d: %d %d\n", c, d);
return 0;
}
Beware that you can only build argument lists for variadic functions in your compile-time. You can't have a dynamically changing list of parameters. In other words, you'll have to hard-code each case you'd ever wanna handle. If the user enters something different, your program will behave very oddly and most probably crash.
The only way is to pass updated arguments directly, since va_list can not be modified. In your case you should parse format string to have an idea about actual content of va_list and then pass compatible set of arguments to fscanf() (not vfscanf()) directly.
It is not possible directly but you can do as below.
int myscanf( char* fmt, ... ) {
va_list ap;
va_start ( ap, fmt );
int newA;
scanf("%d",&new);
vfscanf ( stdin, fmt, ap );
va_end ( ap );
}
I think this will do same as you want.
On a given platform you may use some tricky hack:
va_list is basically a pointer to some data (typically char *),
va_arg is basically pointer arithmetic and cast
So, you can allocate an array of two pointers to int, set the values and call vfscanf with it. Something like:
int *hack[2];
hack[0] = &gC;
hack[1] = &gD;
vscanf(stdin, fmt, (va_list)hack);
BEWARE this is highly non portable, very tricky and error prone. There is a lot of problem with such, even if it basically works on many platforms.
I have the following code:
int __dmasprintf (char **s, const char *format, ...) {
char buf[512];
va_list arg;
int ret;
va_start(arg,format);
ret = vsprintf(buf, format, arg);
va_end(arg);
*s = strdup(buf);
if (*s == NULL) return -1;
return 0;
}
I want to add an argument to the va_list arg before calling vsprintf() because my format contains 1 extra argument at the end.
How to add an argument (for example char * myarg) to the the va_list arg?
Or is it possible to pass vsprintf() a customized list?
You can't.
You either need to make two vsprintf (surely vsnprintf?) calls, or replace your function with a variadic macro, like
#define __dmasprintf(S, FMT, ...) ( \
(*S = do_dmasprintf(FMT, __VA_ARGS__, my_arg)) == NULL ? -1 : 0)
char *do__dmasprintf (const char *format, ...) {
char buf[512];
va_list arg;
int ret;
va_start(arg,format);
ret = vsnprintf(buf, sizeof(buf), format, arg);
va_end(arg);
char *s = strdup(buf);
return s;
}
Notes:
I replaced vsprintf with vsnprintf. There's no reason to use the former here (or pretty much anywhere else)
you ignore ret. Should you?
I kept the macro argument layout similar to the original, but since __VA_ARGS__ must be one-or-more arguments (it can't be empty), that means at least one argument is required after FMT. Just remove the FMT argument entirely if you want to allow zero arguments after it.
There is unfortunately no direct way to do that. There is a reason for that : the stdarg macros take the address in the stack of the last known parameter, and then directly iterate the stack.
If you can use macros, #Useless provided a nice solution - beware, macros can have side effects when you pass variables pre- or post-fixed with ++ or --.
If you want to avoid macros, you will have to write your own variant of vsprintf. No problem for it, just find a source for C stdlib (GNU libc could be a nice start point) and be brave ... hope you can use macros !