printf and casting float arguments

2020-04-11 10:03发布

问题:

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?

回答1:

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.



回答2:

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.)



标签: c casting printf