I would like a function I can call this way:
my_printf(int unimportant,
"%-10s", "a string",
"%5.1f", 1.23,
format_string, value_to_format,
/* possibly more pairs like this */
(char *) NULL);
We may assume that the format strings contain at most one %
escape sequence each.
I would like to use one of the sprintf
family to format the values, perhaps producing a result like "a string , 1.2, …"
.
So far I know several ways that do not work. The following code shows (an incomplete sketch of) the basic idea:
char * my_printf(int uninmportant, ...)
{
va_arg ap;
va_start(ap, unimportant);
while (1) {
char *format = va_arg(ap, char *);
if (!format) break;
vsprintf(buffer, format, ap);
/* append buffer somewhere */
}
}
This doesn't work because there is no guarantee about the usefulness of ap
on return from vsprintf
. It could be pointing anywhere or nowhere. The usual fix for that is to copy ap
with va_copy
before passing it. But that doesn't help in this case:
char * my_printf(int uninmportant, ...)
{
va_arg ap;
va_start(ap, unimportant);
while (1) {
char *format = va_arg(ap, char *);
if (!format) break;
va_arg ap_copy;
va_copy(ap_copy, ap);
vsprintf(buffer, format, ap_copy);
/* append buffer somewhere */
/* ?? seek ap forward to point to the next format string ?? */
}
}
Now vsprintf
no longer destroys ap
. But "seek ap
forward to point to the next format string" seems quite difficult, because it seems to involve parsing and interpreting the contents of format
, at least sufficiently well to know what type to pass to va_arg
.
Similarly if I try to get around the problem by using sprintf
, I still have to parse the format string in order to uunderstand how to call sprintf
:
char * my_printf(int uninmportant, ...)
{
va_arg ap;
va_start(ap, unimportant);
while (1) {
char *format = va_arg(ap, char *);
if (!format) break;
??TYPE?? arg = va_arg(ap, ??TYPE??);
sprintf(buffer, format, arg);
/* append buffer somewhere */
}
}
Again it seems the only way around this is to parse the value of format
and then have a big switch
with an arm for each possible type.
I can't have been the first or the thousandth person to want to do this. What is the conventional wisdom here?
my_printf
calling syntax is misconceived. Have it be called this other way instead: …I am aware that some compilers, such as GCC, provide nonportable extensions to assist with this, such as parse_printf_format
, but I would like a more portable solution.
Thanks for any suggestions.
But "seek
ap
forward to point to the next format string" seems quite difficult, because it seems to involve parsing and interpreting the contents offormat
, at least sufficiently well to know what type to pass tova_arg
.
Yes, you cannot definedly advance through the elements of va_list
without knowing (closely enough) the type of each element. This is a basic limitation of C variadic functions and the stdarg.h macros.
it seems the only way around this is to parse the value of format and then have a big switch with an arm for each possible type.
Yes, that's pretty much right. The job is a bit simplified because
the arguments will have been subject to lvalue conversion, which moots all type qualifiers
the default argument promotions will have been performed on the argument values, which reduces the number of types you need to consider
you need only compatible types, not necessarily exact matches, though in practice, the above points probably moot this.
there are a few exceptions to needing even compatible types that might also reduce the number of type you need to consider (see C23 7.16.2.2./2)
your problem domain may also limit the number of types you need to consider. For example, the printf
family of functions does not support arbitrary pointers, but rather only pointers to char
and pointers to void
, and those two can be used interchangeably with the stdarg macros. And under some circumstances (related to the actual argument values supported), you might be able to handle unsigned integer types as the corresponding signed integer type, or vice versa.
Ultimately, however, you need to apply knowledge of the types of the arguments passed in order to advance through the list. More than just argument size, this accounts for the possibility of altogether different argument-passing mechanisms, such as which registers, if any, arguments of different types might be passed in. There is no standard shortcut for this.