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

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

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

Ответ 1

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

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

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

Кроме того, , я полагаю, что причина (опустить %hf с указанием float -программировано до double в caller-in 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), с поведением undefined, когда значение двойной точности не привязано для float -s и symetrical %hf, принятое указателями scanf для float. Удачи вам в этом.

Ответ 2

Из-за рекламных акций по умолчанию.

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

C11 §6.5.2.2 Функциональные вызовы

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

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

Ответ 3

Из-за рекламных акций по умолчанию при вызове вариативных функций значения 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 приводит к поведению undefined, и нет такой вещи, как unsigned float. Образец, который вы видите, не имеет смысла.


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

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

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

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

... и %Ld не означает long. Это означает undefined поведение.

Ответ 4

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

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

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

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

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

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

Ответ 5

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

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

Ответ 6

Считая логику C, ниже fscanf, можно найти следующее:

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

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

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

Ответ 7

Когда 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).

Ответ 8

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

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

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