printf по заданному указателю и форматной строке. Проблема с поплавками - PullRequest
5 голосов
/ 08 сентября 2011

Поскольку я хочу отслеживать некоторые переменные, чтобы увидеть, как они меняются, я хочу создать функцию, которая получает строку формата в зависимости от типа переменной и указателя на значение.Я надеюсь, что с помощью строки формата printf правильно определит значение.На самом деле это работает, но с одним исключением - значения с плавающей точкой не печатаются должным образом.

Это мой код:

#include <stdio.h>

void pprint (char* fmtstr, void* p)
{
        printf(fmtstr,*(long double *)p);
}

int main (int argc, char **argv)
{
        char cval = 64;
        int ival = -534;
        unsigned int iuval = 535;
        float fval = 534.64;
        double dval = 53432.1;
        long double ldval = 534321234.134567;
        long long lval = -654321;
        unsigned long long luval = 7654321;

        pprint ("char: %hhd\n",&cval);
        pprint ("int: %d\n",&ival);
        pprint ("uint: %u\n",&iuval);
        pprint ("float: %f\n",&fval);
        pprint ("double: %f\n",&dval);
        pprint ("long double: %Lf\n",&ldval);
        pprint ("llong: %lld\n",&lval);
        pprint ("ullong: %llu\n",&luval);
        return 0;
}

И результаты:

char: 64
int: -534
uint: 535
float: 0.000000
double: 53432.100000
long double: 534321234.134567
llong: -654321
ullong: 7654321

Как мы видим, все напечатано нормально, кроме поплавка.Однако после некоторой модификации функции pprint (приведение пустого указателя к плавающей точке):

printf(fmtstr,*(float*)p);

Результаты будут такими:

char: 0
int: 1073741824
uint: 0
float: 534.640015
double: 0.000000
long double: 0.000000
llong: -351285912010752
ullong: 4038940431088615424

Теперь только значение с плавающей точкой печатается правильно.Другим побочным эффектом является то, что приведение к любому другому типу приводит к успешной печати типов с меньшим или одинаковым размером.(Если я приведу к int, он будет правильно печатать символы, но не длинные).Таким образом, приведение к long double решает эту проблему, поскольку он имеет наибольший размер.

Однако проблема с плавающей точкой остается.Чтобы напечатать значение с плавающей точкой, мне нужно привести указатель к плавающей точке, но не более того.И наоборот: когда я бросаю на плаву, все, кроме плавания, не получается.Разве разыменование не связано со считыванием данных, расположенных по указанному адресу, и передачей их в printf?(тот же указатель, приведенный к любому типу, содержит тот же адрес?) Затем строка формата может «читать» данные байты в правильном формате - char, int, unsigned int и т. д.

Iслышал, что значения с плавающей запятой в функциях с переменными числами преобразуются в двойные.Это связано с проблемой?Кроме того, в некоторых случаях я не могу предоставить значение как double - потому что, например, многие из переменных, используемых в Opengl, являются числами с плавающей запятой (GLfloat).

В итоге у меня есть 2 вопроса.

  1. Почему поплавки ведут себя не так, как все остальные типы.

  2. Может ли использование этой функции привести к побочным эффектам?Я имею в виду, что при печати, например, int, printf получает 12 байтов (sizeof (long double)) в качестве второго параметра, но читает только 4 (строка формата "% d").Из этих 12 байтов первые 4 принадлежат значению int, которое я хочу напечатать, а следующие 8 являются ненужными, которые никогда не читаются printf.Это проблема?

Спасибо.

Ответы [ 4 ]

4 голосов
/ 08 сентября 2011

Это не отвечает ни на один из ваших вопросов, но вы можете посмотреть vprintf , если пытаетесь обернуть printf.

4 голосов
/ 08 сентября 2011

Подход, который вы пытаетесь использовать, даже отдаленно не жизнеспособен.Это не может быть сделано таким образом.Если вы настаиваете на интерфейсе «строка формата и указатель» для вашей функции-оболочки printf, то единственный способ заставить ее работать - это вручную проанализировать строку формата, определить фактический тип аргумента и привести (и разыменовать) значение указателя с правильным типом.В этом случае не существует решения «один бросок подходит всем».

Ваши вопросы не имеют однозначного ответа в общем случае.Ваша программа имеет неопределенное поведение по нескольким причинам.Конкретное практическое поведение будет зависеть от реализации и, возможно, от других факторов.

Опять же, единственный способ заставить его работать - это вручную проанализировать строку формата, что также не является хорошей идеей.Если бы вы могли объяснить, откуда появился интерфейс «строка формата и указатель», возможно, мы могли бы предложить более разумный интерфейс.

4 голосов
/ 08 сентября 2011

Вам повезло (или не повезло), он напечатал то, что вы ожидали для многих из этих типов.

Стандарт C гласит, в §7.19.6.1 / 9

Если какой-либо аргумент не является правильным типом для соответствующей спецификации преобразования, поведение не определено.

В частности, во многих реализациях используются разные регистры ЦП для передачи чисел с плавающей точкой и целых чисел в printf (), и даже если в исходном коде записано целое число, «% f» делает printf () доступ к регистру с плавающей запятой, или наоборот.

Например, на моей системе вывод ваших программ:

char: -1
int: 164124920
uint: 164124916
float: 0.000000
double: 0.000000
long double: 534321234.134567
llong: 140733357512904
ullong: 140733357512896

и

char: -17
int: 1200847080
uint: 1200847076
float: 534.640015
double: 0.000000
long double: 0.000000
llong: 140734394235064
ullong: 140734394235056
1 голос
/ 08 сентября 2011
  1. Плавания ведут себя по-разному, потому что спецификатор формата ожидает 32-битный размер, а вы отправляете ему 64-битный тип. Для других типов вам повезло с порядком байтов.

  2. Перечисление побочных эффектов неправильного использования библиотечной функции является спорным.

Исправьте код. Я бы посмотрел на функции vprintf() и vsprintf(), или, что еще лучше, констатировал ваши цели проектирования, возможно, было бы более простое и жизнеспособное решение, чем код, который вы показали.

...