Почему спецификатор для `float` не определен в`printf`?

Похоже, что могло бы быть, есть (по крайней мере, в C99) модификаторы длины, которые могут быть применены к int: %hhd, %hd, %ld а также %lld имею в виду signed char, short, long а также long long, Существует даже модификатор длины, применимый к double: %Lf средства long double,

Вопрос в том, почему они опустили float? Следуя шаблону, это могло быть %hf,

8 ответов

Потому что в вызовах C-функций float аргумент повышается (то есть преобразуется) в double, так printf получает double и будет использовать va_arg(arglist, double) чтобы получить его внутри его реализации.

В прошлом (C89 и K&R C) каждый float аргумент был преобразован в double, Текущий стандарт пропускает это продвижение для функций фиксированной арности, которые имеют явный прототип. Это связано с (и подробности объяснены в) ABI и соглашениями о вызовах реализации. Практически говоря, float Значение часто загружается в регистр с плавающей запятой при передаче в качестве аргумента, но детали могут отличаться. Прочитайте в качестве примера спецификацию ABI для Linux x86-64.

Кроме того, нет практической причины давать конкретную строку управления форматом для float так как вы можете настроить ширину вывода (например, с помощью %8.5f) как и хотел, и %hd гораздо полезнее (почти необходимо) в scanf чем в printf

Кроме того, я думаю, что причина (чтобы опустить %hf указав float - повышен до double в вызывающей printf) является историческим: сначала C был языком системного программирования, а не HPC (Fortran предпочитался в HPC, возможно, до конца 1990-х годов) и float было не очень важно; это было (и есть) мысль о том, как short способ снизить потребление памяти. А современные FPU достаточно быстрые (на настольных или серверных компьютерах), чтобы избежать использования float кроме как для использования меньшего количества памяти. Вы в основном должны верить, что каждый float где-то (возможно, внутри FPU или CPU) преобразован в double,

На самом деле ваш вопрос можно перефразировать так: почему %hd существует для printf (где это в принципе бесполезно, так как printf получает int когда ты передашь short; тем не мение scanf нужно!) Я не знаю почему, но я думаю, что в системном программировании это может быть более полезным.

Вы можете потратить время на лоббирование следующего стандарта ISO C, чтобы получить %hf принят printf за float (повышен до double в printf звонки, как short -S получить повышение в int), с неопределенным поведением, когда значение двойной точности выходит за пределы float и симметрично %hf принят scanf за float указатели. Удачи на этом.

Из-за продвижения аргумента по умолчанию.

printf() переменная функция аргумента (... в своей подписи) все float доводы доводятся до double,

C11 §6.5.2.2 Вызовы функций

6 Если выражение, обозначающее вызываемую функцию, имеет тип, который не включает в себя прототип, целочисленные преобразования выполняются для каждого аргумента и аргументов, которые имеют тип float повышены до double, Это называется продвижением аргументов по умолчанию.

7 Многоточие в объявлении прототипа функции приводит к тому, что преобразование типа аргумента останавливается после последнего объявленного параметра. Повышение аргументов по умолчанию выполняется на конечных аргументах.

Из-за продвижения аргументов по умолчанию при вызове функций с переменным числом float значения неявно преобразуются в double до вызова функции, и нет возможности передать float значение для printf, Так как нет возможности передать float значение для printf нет необходимости в явном спецификаторе формата для float ценности.

Сказав это, AntoineL поднял интересный момент в комментарии, что %lf (в настоящее время используется в scanf соответствовать типу аргумента double *) возможно, когда-то стоял за long float ", который был синонимом типа в дни до C89, согласно странице 42 обоснования C99. По этой логике, возможно, имеет смысл %f должен был стоять за float значение, которое было преобразовано в double,


Учитывая hh а также h модификаторы длины, %hhu а также %hu представить четко определенный вариант использования для этих спецификаторов формата: вы можете напечатать младший значащий байт большого unsigned int или же unsigned short без приведения, например:

printf("%hhu\n", UINT_MAX); // This will print (unsigned char)  UINT_MAX
printf("%hu\n",  UINT_MAX); // This will print (unsigned short) UINT_MAX

Не совсем ясно, что такое сужение конверсии от int в char или же short приведет к, но это, по крайней мере, определяется реализацией, то есть реализация необходима для фактического документирования этого решения.

Следуя шаблону, это должно было быть %hf,

Следуя шаблону, который вы наблюдали, %hf следует преобразовать значения за пределы диапазона float вернуться к float, Тем не менее, этот вид сужения преобразования из double в float приводит к неопределенному поведению, и нет такой вещи, как unsigned float, Шаблон, который вы видите, не имеет смысла.


Чтобы быть формально правильным, %lf не обозначает long double аргумент, и если вы должны были передать long double аргумент, который вы будете вызывать неопределенное поведение. Из документации ясно, что:

l (ell) ... не влияет на следующее a, A, e, E, f, F, g, или же G спецификатор конверсии.

Я удивлен, что никто больше не понял это? %lf обозначает double аргумент, так же, как %f, Если вы хотите напечатать long double использовать %Lf (заглавная буква) .

Впредь должно быть понятно, что %lf для обоих printf а также scanf соответствовать double а также double * аргументы... %f является исключительным только из-за продвижения аргумента по умолчанию, по причинам, упомянутым ранее.

... а также %Ld не значит long, или. Это означает неопределенное поведение.

Из стандарта ISO C11, 6.5.2.2 Function calls /6 а также /7, обсуждая вызовы функций в контексте выражений (мой акцент):

6 / Если выражение, обозначающее вызываемую функцию, имеет тип, который не включает в себя прототип, целочисленные преобразования выполняются для каждого аргумента, а аргументы, имеющие тип float, повышаются до двойного. Это называется продвижением аргументов по умолчанию.

7 / Если выражение, обозначающее вызываемую функцию, имеет тип, который включает в себя прототип, аргументы неявно преобразуются, как если бы посредством присваивания, в типы соответствующих параметров, принимая тип каждого параметра как неквалифицированную версию его объявленный тип. Многоточие в объявлении прототипа функции останавливает преобразование типа аргумента после последнего объявленного параметра. Повышение аргументов по умолчанию выполняется на конечных аргументах.

Это означает, что любой float аргументы после ... в прототипе преобразуются в double и printf семейство звонков определяется таким образом (7.21.6.11 et seq):

int fprintf(FILE * restrict stream, const char * restrict format, ...);

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

Принимая во внимание scanf имеет отдельные спецификаторы формата для float, double или long double, я не понимаю, почему printf и аналогичные функции не были реализованы подобным образом, но так в итоге оказались C / C++ и стандарты.

Может быть проблема с минимальным размером для операции push или pop в зависимости от процессора и текущего режима, но это может быть обработано с заполнением по умолчанию, аналогично выравниванию по умолчанию локальных переменных или переменных в структуре. Microsoft отказалась от поддержки 80 бит (10 байт) long doubleкогда он перешел с 16-битных на 32 / 64-битные компиляторы, теперь лечится long doubleтак же, как doubleс (64 бита / 8 байт). Они могли бы дополнить их до 12 или 16-байтовых границ по мере необходимости, но это не было сделано.

Когда C был изобретен, все значения с плавающей точкой были преобразованы в общий тип (т.е. double) перед использованием в вычислениях или передачей в функции (включая) printfтак что не было необходимости printf проводить какие-либо различия между типами с плавающей точкой.

В целях повышения арифметической эффективности и точности стандарт IEEE-754 определил 80-битный тип, который был больше, чем обычный 64-битный. double но может быть обработано быстрее. Намерение состояло в том, чтобы дать выражение как a=b+c+d; было бы быстрее и точнее преобразовать все в 80-битный тип, сложить три 80-битных числа и преобразовать результат в 64-битный тип, чем вычислять сумму (b+c) как 64-битный тип, а затем добавить это к d,

В интересах поддержки нового типа ANSI C определил новый тип long double какие реализации могут иметь ссылку на новый 80-битный тип или 64-битный double, К сожалению, несмотря на то, что цель 80-битного типа IEEE-754 состояла в том, чтобы все значения автоматически переводились в новый тип так, как они были представлены. double, ANSI сделал это так, чтобы новый тип был передан printf или другие вариационные методы, отличные от других типов с плавающей точкой, что делает такое автоматическое продвижение невыгодным.

Следовательно, оба типа с плавающей точкой, которые существовали при создании C, могут использовать один и тот же %f спецификатор формата, но long double который был создан позже, требует другого %Lf спецификатор формата (с заглавной буквы L).

%hhd, %hd, %ld а также %lld были добавлены к printf чтобы сделать строки формата более совместимыми с scanfдаже если они избыточны для printf из-за продвижения аргумента по умолчанию.

Так почему не было %hf добавлено для float? Это легко: смотреть на scanfповедение, float уже имеет спецификатор формата. Это %f, И спецификатор формата для double является %lf,

Тот %lf это именно то, что C99 добавил к printf, До C99 поведение %lf был неопределенным (из-за пропуска любого определения в стандарте). Начиная с C99, это синоним %f,

Читая обоснование ниже fscanf, можно найти следующее:

Новая функция C99: в C99 были добавлены модификаторы длины hh и ll. ll поддерживает новый тип long long int. hh добавляет возможность обрабатывать типы символов так же, как и все другие целочисленные типы; это может быть полезно при реализации макросов, таких как SCNd8 в (см. 7.18).

Так предположительно hh был добавлен с целью оказания поддержки всем новым stdint.h типы. Это может объяснить, почему модификатор длины был добавлен для маленьких целых чисел, но не для маленьких чисел с плавающей точкой.

Это не объясняет, почему С90 h но нет hh хоть. Язык, указанный в C90, не всегда последовательный, простой. И более поздние версии унаследовали несоответствие.

Другие вопросы по тегам