As a part of my program I use:
int ret = vprintf (format, args);
The args
I get on the stack and I can't know what actually was pushed on the stack.
The format is a string, which I can read.
The above approach works until I have to print floats. When I print float I get some strange numbers ...
I checked that if I call float fArg = *(reinterpret_cast<const float*>(args)
- and then print fArg
the correct value is printed (I tried it when args was consisted only from one actual argument)
So probably I need special behavior for "%...f"
sub-format - the corresponding (sub)argument should be cast
to float. (The ...
notation means that precision, width etc. could be added before f
)
How can I implement it?
Note that with variable-length argument lists, all float
values are promoted to (and passed as) double
values. You cannot reliably use:
float f = va_arg(args, float); /* BAD! */
because the language never places a float value on the stack. You would have to write:
float f = va_arg(args, double); /* OK */
This may be your entire problem.
If not, it is likely that you will need to scan the format string, and isolate the format specifiers, and implement a significant portion of the core printf()
code. For each specifier, you can collect the appropriate value from the args
. You then simply call the appropriate printf()
function on a copy of the initial segment of the format string (because you can't modify the original) with the correct value. For your special case, you do whatever it is you need to do differently.
It would be nice to be able to pass the args
parameter to vprintf()
so it deals with collecting the type, etc, but I don't think that's portable (which is undoubtedly a nuisance). After you've passed a va_list
value such as args
to a function that uses va_arg()
on it, you cannot reliably do anything other than va_end()
on the value after the function returns.
Earlier this year, I wrote an printf()
-style format string analyzer for POSIX-enhanced format strings (which support the n$
notation to specify which argument specifies a particular value). The header I created contains (along with enumerations for PFP_Errno
, PFP_Status
, FWP_None
and FWP_Star
):
typedef struct PrintFormat
{
const char *start; /* Pointer to % symbol */
const char *end; /* Pointer to conversion specifier */
PFP_Errno error; /* Conversion error number */
short width; /* Field width (FPW_None for none, FPW_Star for *) */
short precision; /* Field precision (FPW_None for none, FPW_Star for *) */
short conv_num; /* n of %n$ (0 for none) */
short width_num; /* n of *n$ for width (0 for none) */
short prec_num; /* n of *n$ for precision (0 for none) */
char flags[6]; /* [+-0# ] */
char modifier[3]; /* hh|h|l|ll|j|z|t|L */
char convspec; /* [diouxXfFeEgGAascp] */
} PrintFormat;
/*
** print_format_parse() - isolate and parse next printf() conversion specification
**
** PrintFormat pf;
** PFP_Status rc;
** const char *format = "...%3$+-*2$.*1$llX...";
** const char *start = format;
** while ((rc = print_format_parse(start, &pf)) == PFP_Found)
** {
** ...use filled in pf to identify format...
** start = pf.end + 1;
** }
** if (rc == PFP_Error)
** ...report error, possibly using print_format_error(pf.error)...
*/
extern PFP_Status print_format_parse(const char *src, PrintFormat *pf);
extern const char *print_format_error(PFP_Errno err);
extern PFP_Status print_format_create(PrintFormat *pf, char *buffer, size_t buflen);
The parse function analyzes the source and sets the appropriate information in the structure. The create function takes a structure and creates the corresponding format string. Note that the conversion specifier in the example (%3$+-*2$.*1$llX
) is valid (but a little dubious); it converts an unsigned long long
integer passed as argument number 3 with a width specified by argument 2 and a precision specified by argument 1. You probably could have a longer format, but only by a couple of characters without repetition, even if you used tens or hundreds of arguments in total.
There's no easy, portable way to do this; to inspect a va_list
, you must know what types of values it holds and the only way to know is by parsing the format string. Effectively, you'll have to reimplement part of vprintf
. (Part, because you can still send off the individual format specifier + cast value pairs to printf
and not worry about how to pick apart a float
.)