How to Pass Variable Number of Arguments to Printf/Sprintf

How to pass variable number of arguments to printf/sprintf

void Error(const char* format, ...)
{
va_list argptr;
va_start(argptr, format);
vfprintf(stderr, format, argptr);
va_end(argptr);
}

If you want to manipulate the string before you display it and really do need it stored in a buffer first, use vsnprintf instead of vsprintf. vsnprintf will prevent an accidental buffer overflow error.

Pass varargs to printf

Use vprintf, which is declared as:

int vprintf(const char *format, va_list ap);

In your log function, invoke va_start to obtain a va_list value, then pass that value to vprintf.

Since your log function takes a FILE* argument, you'll probably want to use vfprintf rather than vprintf (and perhaps update your question to ask about fprintf rather than printf).

Incidentally, you might want to reconsider using the name log; that's the name of a standard function declared in <math.h>.

Reflecting your updated question, you can print the timestamp inside log by calling fprintf directly:

va_list(args);
fprintf(f, "%s - ", now());
va_start(args, format);
vfprintf(f, format, args);

how to print a variable number of arguments in r?

The doc is right. In first case, do.call(sprintf, c(list("%s %s"), vec)) is equal to:

sprintf("%s %s", "a","b","c","d")

The fmt string "%s %s" requires two vectors while you provided four and the last two ("c", "d") were not used for printing.

The second case is similar. do.call(sprintf, c(fmt = base_string, as.list(v1))) is equal to:

sprintf(fmt = "%s, %s, %s", "foo", "bar", "baz","foo", "bar", "baz")

Three variables were to be printed based on fmt but you provided six.


Then, what does "recycled" in the doc mean?

and I guess you might misunderstand it. It means when the formating string and vectors are of different lengths, the short er ones will be recycled to the longest one. An example:

> sprintf(c('%s %s', '%s, %s'), c('a', 'b', 'c'), 1:6)
[1] "a 1" "b, 2" "c 3" "a, 4" "b 5" "c, 6"

How to print variable number of arguments: You can try paste:

> sprintf(paste0(vec, collapse = ' '))
[1] "a b c d"
> sprintf(paste0(v1, collapse = ', '))
[1] "foo, bar, baz, foo, bar, baz"

How can I pass variable number of arguments to sprintf() function?

What you want is probably the vsprintf function. It takes an array as the set of arguments. So in your case, you'd have something like this:

$args = <array_of_chosen_options>;
$fmt = trim(str_repeat("%s ", count($args)));
$result = vsprintf($fmt, $args);

Printing a variable number of arguments to a format string

As others said there is no direct way of doing that. You can build your own function which dumps the values of strings at the correct format specifiers. Below function makes a temporary format string for each %s and appends it to the earlier build string using snprintf().

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXBUF 4096

char *strmaker(char* format, int num_args, char** strings)
{
char* prnt = calloc(sizeof(char), MAXBUF);
int prnt_ct = 0;
char* tmp_fmt = malloc(strlen(format) + 1); // Prepare for the worst case (format == tmp_fmt).
int fmt_ct = 0;

/* Append the strings to the prnt buffer */

for (int i = 0; i < num_args; i++) {
char* s_loc = strstr(format + fmt_ct, "%s"); // Search the format-string for string specifier (%s)
if (s_loc == NULL)
return prnt;

int tmp_fmt_len = (int) (s_loc + 2 - format - fmt_ct); // +2 for %s
strncpy(tmp_fmt, format + fmt_ct, tmp_fmt_len); // Make tmp_fmt
tmp_fmt[tmp_fmt_len] = '\0';
fmt_ct = fmt_ct + tmp_fmt_len;

int p_return = snprintf(prnt + prnt_ct, MAXBUF - prnt_ct, tmp_fmt, strings[i]); // If no error, return the number characters printed excluding nul (man page)

if (p_return >= MAXBUF - prnt_ct) // If buffer overflows (man page)
return prnt;

prnt_ct = prnt_ct + p_return; // Update the index location.
}

return prnt;
}

int main(int argc, char *argv[]) // Pass format and arguments
{
if (argc <= 1)
return -1;

char *s = strmaker(argv[1], argc - 2, argv + 2);
printf("%s\n", s);
free(s);

return 0;
}

Terminal Session:

$ ./a.out '%s %s %s' 1 2 3 
1 2 3
$ ./a.out 'one %s two %s three %s' 1 2 3
one 1 two 2 three 3
$ ./a.out 'one %s two %s three' 1 2 3
one 1 two 2
$ ./a.out 'one %s two %s three %s' 1 2
one 1 two 2

printf - Can I pass all the variadic arguments as a pointer instead of one-by-one on the stack?

A solution will involve writing your own format string parser that interprets the format specifiers with data width to extract from the binary blob, such that each format specifier caused the data index to be incremented, and the correct type to be extracted and printed.

Superficially the function vprintf appears close to what you need, but is not viable. It takes a single va_list argument which normally you create from a ... var-arg list using using va_start, but in your case the input is not a var-arg list, so it is unlikely to work directly on the binary data without first unpacking your binary data - other than by dumb luck, and even then certainly not portable.

The (or at least one) problem with vprintf is a var-arg list is unlikely to be aligned in the same manner as your binary blob; stacked arguments are likely to have fixed alignment and not be contiguous.

The approach I'd suggest here is to parse the format string, print each non-format specifier character directly, extract format specifiers completely (with all modifiers), determine the width/type from the specifier, extract that much data into an appropriate data type, print that single data item using the whole format specifier, and increment the data index by the width. Continue like that for the entire format string. It is a lot of work - you'd have to be doing a lot of this to justify the effort - but you only write it once, and it would be generic so may make maintenance simpler.

Note:

My motivation for wanting such a solution is faster post-processing thanks to less copying of the data.

That is a poor reason; it will have negligible performance impact. It is better justified by the simplicity and convenience of encoding arbitrary data structure of the binary blob in the format string. This may have a significant development, readability and maintenance impact if you have a lot of code like this with many varying data structures to encode.

How to resolve variable number of arguments problem of printf if reverse printing is not allowed

If you were to design a new compiler for a different calling convention, you could have the compiler push the number of actual arguments with which the call was done, or set that number in a specific register such as RAX/EAX, which will be overridden anyway.

Another option would be to redefine the printf() API to have the format string as the last parameter. In this way you will have all you need to access the stack looking for your parameters.

Variable number of arguments in C programmng

I want to use functions like sum(1,2,3) should return 6. i.e, no counter should be there

You could define a sentinel. In this case 0 might make sense.

/* Sums up as many int as required. 
Stops adding when seeing the 1st 0. */
int sum(int i, ...)
{
int s = i;

if (s)
{
va_list ap;

va_start(ap, i);

/* Pull the next int from the parameter list and if it is
equal 0 leave the while-loop: */
while ((i = va_arg(ap, int)))
{
s += i;
}

va_end(ap);
}

return s;
}

Call it like this:

int sum(int i, ...);

int main(void)
{
int s = sum(0); /* Gives 0. */

s = sum(1, 2, 3, 0); /* Gives 6. */
s = sum(-2, -1, 1, 2, 0); /* Gives 0. */
s = sum(1, 2, 3, 0, 4, 5, 6); /* Gives 6. */

s = sum(42); /* Gives undefined behaviour! */
}

The sum() function alternatively could also look like this (but would do one useless addition of 0):

/* Sums up as many int as required. 
Stops adding when seeing the 1st 0. */
int sum(int i, ...)
{
int s = i;

if (s)
{
va_list ap;

va_start(ap, i);

/* Pull the next int from the parameter list and if it is
equal 0 leave the do-loop: */
do
{
i = va_arg(ap, int);
s += i;
} while (i);

va_end(ap);
}

return s;
}

How to write a function with a variable number of arguments that are passed to printf

Use an intermediate variable (here substr) to build the functional message (date message) and a second buid for the technical message (error message):

#! /bin/bash

declare -r script_name="tmp.bash"

function print_error {
local substr=
printf -v substr "$1" "${@:2}"
printf "%s: ERROR: %s\n" "$script_name" "$substr"
}

print_error "Today is %s; tomorrow is %s" "$(date)" "$(date -d "+1 day")"

Than, you can separate functional and technical builds:

#! /bin/bash

declare -r script_name="tmp.bash"

function print_tech_error () {
printf "%s: ERROR: %s\n" "$script_name" "$1"
}
function print_fct_error {
local substr=
printf -v substr "$1" "${@:2}"
print_tech_error "${substr}"
}

print_fct_error "Today is %s; tomorrow is %s" "$(date)" "$(date -d "+1 day")"


Related Topics



Leave a reply



Submit