printf и приведение аргументов с плавающей запятой

#c #casting #printf

#c #Кастинг #printf

Вопрос:

Как часть моей программы я использую:

 int ret = vprintf (format, args);
  

args Я попадаю в стек, и я не могу знать, что на самом деле было помещено в стек.
Формат представляет собой строку, которую я могу прочитать.

Описанный выше подход работает до тех пор, пока мне не придется печатать значения с плавающей запятой. Когда я печатаю float, я получаю какие-то странные числа…

Я проверил, что если я вызываю float fArg = *(reinterpret_cast<const float*>(args) — а затем печатаю fArg , выводится правильное значение (я пробовал это, когда аргументы состояли только из одного фактического аргумента)

Поэтому, вероятно, мне нужно особое поведение для "%...f" подформата — соответствующий (sub) аргумент должен быть приведен к float. ( ... Обозначение означает, что точность, ширина и т.д. Могли быть добавлены раньше f ) Как я могу это реализовать?

Комментарии:

1. Если вы не знаете, что это за тип, как вы могли бы его преобразовать?

2. Я знаю, что аргумент, который должен быть напечатан для %f, должен быть явно приведен к плавающей точке. Если нет плавающих значений, напечатанных vprintf (формат, аргументы); работает нормально

3. Но приведение к float (double) — это то, что printf собирается делать внутренне. Неясно, что, по вашему мнению, вам нужно здесь делать или почему.

4. @Yakov — reinterpret_cast ? Какой тип C вы пишете? 😉

5. @Yakov: Тогда вы программируете на C , а не C.

Ответ №1:

Обратите внимание, что в списках аргументов переменной длины все float значения преобразуются в double значения (и передаются как). Вы не можете надежно использовать:

 float f = va_arg(args, float);  /* BAD! */
  

потому что язык никогда не помещает значение с плавающей запятой в стек. Вам пришлось бы написать:

 float f = va_arg(args, double);  /* OK */
  

Возможно, в этом и заключается вся ваша проблема.


Если нет, то, вероятно, вам потребуется отсканировать строку формата, изолировать спецификаторы формата и реализовать значительную часть основного printf() кода. Для каждого спецификатора вы можете получить соответствующее значение из args . Затем вы просто вызываете соответствующую printf() функцию для копии начального сегмента строки формата (поскольку вы не можете изменить оригинал) с правильным значением. В вашем особом случае вы делаете все, что вам нужно сделать по-другому.

Было бы неплохо иметь возможность передавать args параметр в vprintf() , чтобы он занимался сбором типа и т.д., Но я не думаю, что это переносимо (что, несомненно, является неприятностью). После того, как вы передали va_list значение, такое как args , в функцию, которая использует va_arg() для него, вы не можете надежно ничего сделать, кроме va_end() для значения после возврата функции.


Ранее в этом году я написал анализатор строк формата в printf() стиле для строк формата, улучшенных POSIX (которые поддерживают n$ обозначения, чтобы указать, какой аргумент задает конкретное значение). Созданный мной заголовок содержит (наряду с перечислениями для PFP_Errno , PFP_Status , FWP_None и 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, amp;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);
  

Функция parse анализирует источник и устанавливает соответствующую информацию в структуре. Функция create принимает структуру и создает соответствующую строку формата. Обратите внимание, что спецификатор преобразования в примере ( %3$ -*2$.*1$llX ) является допустимым (но немного сомнительным); он преобразует unsigned long long целое число, переданное в качестве аргумента номер 3, с шириной, указанной аргументом 2, и точностью, указанной аргументом 1. Вероятно, у вас мог бы быть более длинный формат, но только на пару символов без повторений, даже если вы использовали в общей сложности десятки или сотни аргументов.

Комментарии:

1. 1 за автоматическое перемещение float аргументов в double — я предполагаю, что это проблема … и за предоставление достаточного количества, чтобы указать, что такого рода вещи далеки от тривиальных!

Ответ №2:

Простого переносимого способа сделать это не существует; чтобы проверить va_list , вы должны знать, какие типы значений он содержит, и единственный способ узнать это — проанализировать строку формата. По сути, вам придется переопределить часть vprintf . (Частично, потому что вы все еще можете отправить отдельные пары спецификатор формата приведенное значение в printf и не беспокоиться о том, как выделить float .)