看起来可能是这样,有(至少在 C99 中)长度修饰符可以应用于int
: %hhd
、和mean %hd
、和. 甚至还有一个适用于:的长度修饰符。%ld
%lld
signed char
short
long
long long
double
%Lf
long double
问题是为什么他们省略了float
?按照模式,它可能是%hf
。
看起来可能是这样,有(至少在 C99 中)长度修饰符可以应用于int
: %hhd
、和mean %hd
、和. 甚至还有一个适用于:的长度修饰符。%ld
%lld
signed char
short
long
long long
double
%Lf
long double
问题是为什么他们省略了float
?按照模式,它可能是%hf
。
因为在 C 可变参数函数调用中,任何float
参数都被提升(即转换)为 a double
,因此printf
获取 adouble
并将用于va_arg(arglist, double)
在其实现中获取它。
在过去(C89 和 K&R C),每个float
参数都转换为double
. 当前标准省略了对具有显式原型的固定数量函数的这种提升。它与实现的ABI和调用约定有关(并在中解释了详细信息) 。实际上,float
当作为参数传递时,通常会将值加载到双浮点寄存器中,但细节可能会有所不同。阅读 Linux x86-64 ABI 规范作为示例。
此外,没有实际理由提供特定的格式控制字符串,float
因为您可以根据需要调整输出的宽度(例如,使用%8.5f
),并且比 in%hd
更有用(几乎必要)scanf
printf
除此之外,我想原因(在 caller-in 中省略%hf
指定-promoted to float
)是历史性的:起初,C 是一种系统编程语言,而不是 HPC 语言(可能直到 1990 年代后期,HPC 中更喜欢 Fortran)和不是很重要;它曾经(现在仍然)被认为是一种降低内存消耗的方法。今天的 FPU 速度足够快(在台式机或服务器计算机上),可以避免使用except 作为使用更少内存的手段。您基本上应该相信每个都在某个地方(可能在FPU 或 CPU 内部)转换为.double
printf
float
short
float
float
double
实际上,您的问题可能被解释为:为什么%hd
存在printf
(它基本上是无用的,因为当您通过它时printf
会得到一个;但是需要它!)。我不知道为什么,但我想它可能比在系统编程中更有用。int
short
scanf
您可以花时间游说下一个 ISO C 标准以被for%hf
接受(提升为at调用,如-s 提升为),当双精度值超出-s 的范围并被for对称接受时,行为未定义指针。祝你好运。printf
float
double
printf
short
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在评论中提出了一个有趣的观点,根据第 42 页,该评论(目前用于对应于参数类型)可能曾经代表“”,这是 C89 之前的%lf
类型scanf
同义词double *
。C99 理由。按照这种逻辑,它可能是有意义的,它旨在代表一个已转换为 a 的值。long float
%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
to的缩小转换会float
导致未定义的行为,并且不存在unsigned float
. 你看到的模式没有意义。
正式正确,%lf
不表示long double
参数,如果您要传递long double
参数,您将调用未定义的行为。文档中明确指出:
l
(ell) ... 对后面a
的 ,A
,e
,E
, ,f
,F
,g
, 或G
转换说明符没有影响。
我很惊讶没有其他人对此有所了解?%lf
表示一个double
参数,就像%f
. 如果要打印 a long double
,请使用%Lf
(大写 ell)。
从今以后应该有意义的是,%lf
对于两者printf
,并且scanf
对应于double
和double *
论点...%f
是例外的,只是因为默认的论点提升,出于前面提到的原因。
......%Ld
也不意味着long
。这意味着未定义的行为。
根据 ISO C11 标准,6.5.2.2 Function calls /6
讨论/7
表达式上下文中的函数调用(我的重点):
6/ 如果表示被调用函数的表达式的类型不包含原型,则对每个参数执行整数提升,而浮点类型的参数将提升为双精度。这些称为默认参数提升。
7/ 如果表示被调用函数的表达式具有包含原型的类型,则参数被隐式转换为相应参数的类型,就像通过赋值一样,将每个参数的类型作为非限定版本它声明的类型。函数原型声明器中的省略号会导致参数类型转换在最后一个声明的参数之后停止。默认参数提升是在尾随参数上执行的。
这意味着原型中的任何float
参数...
都将转换为double
,并且printf
调用系列以这种方式定义(7.21.6.11
et seq):
int fprintf(FILE * restrict stream, const char * restrict format, ...);
因此,由于printf()
-family 调用无法实际接收浮点数,因此为其设置特殊的格式说明符(或修饰符)毫无意义。
发明 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
。不幸的是,尽管IEEE-754 80 位类型的目的double
是让所有值按照它们被提升到的方式自动提升到新类型,但ANSI 还是这样做了,因此新类型被传递给printf
或其他可变参数方法不同于其他浮点类型,从而使这种自动提升是站不住脚的。
因此,创建 C 时存在的两种浮点类型都可以使用相同的%f
格式说明符,但long double
之后创建的浮点类型需要不同的%Lf
格式说明符(带有大写字母 L
)。
%hhd
, %hd
,%ld
和%lld
被添加到printf
使格式字符串与 更加一致scanf
,即使printf
由于默认参数提升,它们是多余的。
那么为什么不%hf
添加 forfloat
呢?这很简单:查看scanf
的行为,float
已经有了格式说明符。是%f
。格式说明符double
是%lf
.
这%lf
正是 C99 添加到printf
. 在 C99 之前, 的行为%lf
是未定义的(通过省略标准中的任何定义)。从 C99 开始,它是%f
.
阅读 fscanf 下面的 C 原理,可以找到以下内容:
C99 的一个新特性:在 C99 中添加了 hh 和 ll 长度修饰符。ll 支持新的 long long int 类型。hh 增加了将字符类型视为与所有其他整数类型相同的能力;这在实现诸如 SCNd8 之类的宏时很有用(参见 7.18)。
所以据说hh
添加的目的是为所有新stdint.h
类型提供支持。这可以解释为什么为小整数添加了长度修饰符,而不是为小浮点数添加了长度修饰符。
它没有解释为什么 C90 不一致h
但没有hh
。C90 中指定的语言并不总是一致的,就这么简单。而后来的版本继承了这种不一致。
考虑到scanf
float、double 或 long double 有单独的格式说明符,我不明白为什么printf
和类似的函数没有以类似的方式实现,但这就是 C/C++ 和标准的最终结果。
取决于处理器和当前模式,推送或弹出操作的最小大小可能存在问题,但这可以通过默认填充处理,类似于结构中局部变量或变量的默认对齐方式。long double
Microsoft在从 16 位编译器升级到 32 / 64 位编译器时放弃了对 80 位(10 字节)的支持,现在将long double
s 视为与double
s(64 位 / 8 字节)相同。他们本可以根据需要将它们填充到 12 或 16 字节边界,但没有这样做。