I have the following log function
void log_error(char * file_name, int line_num, int err_code) {
printf("%s:%d:%s\n", file_name, line_num, get_err_str(err_code));
}
I want to change this to have the option to pass a custom format string and args for additional information something like
void log_error(char * file_name, int line_num, int err_code, ...) {
va_list ptr;
char * fmt;
printf("%s:%d:%s", file_name, line_num, get_err_str(err_code));
va_start(ptr, err_code);
fmt = va_arg(ptr, char *);
vprintf(fmt, ptr);
printf("\n");
}
but I don't want to enforce passing this extra info, so I might not get fmt and the required extra arguments which might cause the code to fail, is there a way to check if there are variadic args before trying to access them? Or the only option for my use case is defining 2 different function, like so
void log_error(char * file_name, int line_num, int err_code) {
printf("%s:%d:%s\n", file_name, line_num, get_err_str(err_code));
}
void log_error(char * file_name, int line_num, int err_code, const char * fmt, ...) {
va_list ptr;
printf("%s:%d:%s", file_name, line_num, get_err_str(err_code));
va_start(ptr, err_code);
vprintf(fmt, ptr);
printf("\n");
va_end(ptr);
}
You can't check for variadic arguments in functions without at least one non-variadic argument that tells you what is going on, but you CAN use a variadic macro that expands to the "right" variadic function call. Since you are probably already using a macro to supply __FILE__ and __LINE__, you can probably do something like:
// this is the function that will be called by the macro
void log_error_func(char * file_name, int line_num, int err_code, const char *fmt, ...);
// this is the macro that will be used to log
#define log_error(CODE, ...) log_error_func(__FILE__, __LINE__, CODE, "" __VA_ARGS__)
Now you can use things like
log_error(42, "this error has args: %d, %d, %d", 1, 2, 3);
as well as
log_error(5); /* no args here */
and they'll work as one would expect. The use of "" in a macro like this requires that the format arg (if present) be string literal, so the empty string will be properly prepended.
Your function doesn't have any way of knowing whether or not a format string has been passed. So the best thing to do is to make it an explicit parameter.
void log_error(char * file_name, int line_num, int err_code, char * fmt, ...) {
va_list ptr;
printf("%s:%d:%s", file_name, line_num, get_err_str(err_code));
va_start(ptr, fmt);
vprintf(fmt, ptr);
va_end(ptr);
printf("\n");
}
For a function like this, I would expect that a message string would have to be passed.
is there a way to check if there are variadic args before trying to access them?
No.
the only option for my use case is defining 2 different function, like so
Generally, yes, or similar.
You can make log_error a macro, and overload the macro on the number of arguments. 3 arguments would go to the first version, more would go to the variadic one.
If you want a single funtion, you MUST have a fmt parameter, but you can pass an empty string ("") as that argument when it's not needed. Or you can modify your code to check if fmt is NULL, if that looks neater to you:
void log_error(char *file_name, int line_num, int err_code, char *fmt, ...) {
printf("%s:%d:%s", file_name, line_num, get_err_str(err_code));
if (fmt != NULL) {
va_list ptr;
va_start(ptr, fmt);
vprintf(fmt, ptr);
va_end(ptr);
}
printf("\n");
}
is there a way to check if there are variadic args before trying to access them?
The variadic argument facility does not itself provide a way to determine how many variadic arguments there are, or whether there are any at all.
Or the only option for my use case is defining 2 different function, like so
A variadic function knows from its non-variadic arguments and from its general contract what variadic arguments to expect, and of what types. printf() is the canonical example here: it knows from its format string what to expect of its variadic arguments.
If all the arguments to the current, non-variadic function have significance unsuited to be adapted to the purpose of informing about the presence of variasic arguments then you need at least a different function signature. You don't necessarily need two functions, though.
If you do use two functions, however, then be aware that they will need different names. C does not support function overloading.
If you want to preserve the current signature for existing callers, yet make the custom-format feature available to new ones, then one option would be to replace the current function with a macro that expands to a call to a replacement function with an augmented argument list. Example:
void log_error_custom(char * file_name, int line_num, int err_code,
const char * fmt, ...) {
// ...
}
#define log_error(file, line, err) log_error_custom((file), (line), (err), "")
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.
If the format string passed to vsprintf() (and variants thereof) contains no %-references, is it guaranteed that the va_list argument is not accessed?
Put another way, is:
#include <stdarg.h>
#include <stdio.h>
int main ( void ) {
char str[16];
va_list ap; /* never initialized */
(void)vsnprintf(str, sizeof(str), "", ap);
return 0;
}
a standard-conforming program? or is there undefined behavior there?
The example above is obviously silly, but imagine a function which can be called by both a variadic function and a fixed-args function, grossly simplified into something like:
void somefuncVA ( const char * fmt, va_list ap ) {
char str[16];
int n;
n = vsnprintf(str, sizeof(str), fmt, ap);
/* potentially do something with str */
}
void vfoo ( const char * fmt, ... ) {
va_list ap;
va_start(fmt, ap);
somefuncVA(fmt, ap);
}
void foo ( void ) {
va_list ap; /* no way to initialize this */
somefuncVA("", ap);
}
int vsprintf(char * restrict s, const char * restrict format, va_list arg);
If the format string passed to vsprintf() ... contains no %-references, is it guaranteed that the va_list argument is not accessed.
No.
The vsprintf function is equivalent to sprintf, with the variable argument list
replaced by arg, which shall have been initialized by the va_start macro .....
C11dr §7.21.6.13
Since the below code does not adhere to the spec, the result is undefined behavior (UB). No guarantees. #Eugene Sh.
va_list ap;
// vv-- ap not initialized
(void)vsnprintf(str, sizeof(str), "", ap);
Is vsprintf() guaranteed not to access va_list if format string makes no % references?
With a properly passed va_list arg, vsprintf() acts like sprintf(). Code like the following is OK. It is permissible to pass extra arguments. Via vsprintf(), they (the extra arguments) are not accessed, yet va_list arg may be accessed.
sprintf(buf, "format without percent", 1.2345, 456)`
If you don't have varargs passed to your function - your function isn't defined with ... as the last parameter - there's simply never any need for any use of va_list or va_start() in that function. If you want to pass an empty set of variable arguments, simply call the varargs function directly without any variable arguments - e.g., printf("\n");.
For example, instead of
void foo ( void ) {
va_list ap; /* no way to initialize this */
somefuncVA("", ap);
}
you can just write
void foo ( void ) {
vfoo("");
}
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
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 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 !