看起来可能是可以的,因为在C99中有长度修改器可以应用于int
: %hhd
, %hd
, %ld
和 %lld
代表signed char
, short
, long
和long long
。甚至有一个适用于double
的长度修改器: %Lf
代表long double
。
问题是为什么他们省略了float
?按照这个模式,它可能会是%hf
。
看起来可能是可以的,因为在C99中有长度修改器可以应用于int
: %hhd
, %hd
, %ld
和 %lld
代表signed char
, short
, long
和long long
。甚至有一个适用于double
的长度修改器: %Lf
代表long double
。
问题是为什么他们省略了float
?按照这个模式,它可能会是%hf
。
float
类型的参数都会被提升(即转换)为double
类型,所以printf
得到的是一个double
类型的值,并且在其内部实现中使用va_arg(arglist, double)
来获取它。float
类型的参数都会被转换为double
类型。当前标准省略了具有显式原型的固定元数函数的这种提升。这与实现的ABI和调用约定有关,详细信息已经解释过了。从实际角度而言,当作为参数传递时,float
类型的值通常会被加载到双精度浮点寄存器中,但细节可能会有所不同。可以阅读Linux x86-64 ABI规范 作为一个例子。float
提供特定的格式控制字符串,因为您可以根据需要调整输出的宽度(例如使用%8.5f
),而%hd
在scanf
中比在printf
中更有用(几乎是必需的)。除此之外,我猜省略指定float
的%hf
(在printf
中被提升为double
)的原因是历史性的:起初,C语言是一种系统编程语言,而不是高性能计算(HPC)语言(直到1990年代后期,Fortran在HPC中更受欢迎),因此float
并不是非常重要;它(仍然)被认为像short
一样,是降低内存消耗的一种方式。今天的浮点数单元(FPU)足够快(在桌面或服务器计算机上),可以避免使用float
,除非作为使用更少内存的手段。基本上,您应该相信每个float
都会在某个地方(可能在FPU或CPU内部)转换为double
。printf
需要%hd
(因为它基本上是无用的,因为当你向其传递一些short
时,printf
会得到一个int
;但是scanf
需要它!)。我不知道为什么,但我想在系统编程中可能更有用。%hf
被printf
接受,用于float
(在printf
调用时升级为double
,就像short
升级为int
一样),当双精度值超出float
的界限时具有未定义的行为,并且对称地,让%hf
被scanf
接受,用于float
指针。祝你好运。printf
的所有内容。除此之外,还有所有窄类型参数向 int
的自动提升,但仍需要特殊的格式修饰符来处理它们。 - Jens Gustedtfloat
提供特定的格式控制字符串”的问题。在某些极端情况下,一些类似"%ha"
的东西可能会有用,以不同的方式对齐显示float
,例如printf("%a %ha\n", FLT_MAX, FLT_MAX) --> 0x1.fffffep+127 0x0.ffffffp+128
,因为第一个例子中讨厌的e
只有3个有效位。 - chux - Reinstate Monica由于默认参数提升的缘故。
printf()
是一个可变参数函数(在其签名中使用...
),所有float
类型的参数都会被提升为double
类型。
C11 §6.5.2.2 函数调用
6 如果表示所调用函数的表达式具有不包含原型的类型,则对每个参数执行整数提升,并将类型为
float
的参数提升为double
类型。 这些称为默认参数提升。7 函数原型声明符中的省略号符号导致参数类型转换在最后声明的参数后停止。 默认参数提升将作用于尾随参数。
int
类型,但它们仍然有特殊的格式修饰符。 - Jens Gustedtfloat
值在函数调用之前会被隐式转换为double
,并且无法将float
值传递给printf
。既然无法将float
值传递给printf
,那么就不需要对float
值使用显式格式说明符。
话虽如此,AntoineL 在评论中提出了一个有趣的观点,即 %lf
(目前在 scanf
中用于对应参数类型 double *
)可能曾经代表“long float
”,这是 C89 之前的一种类型同义词,根据 C99 的理由 第42页。按照这个逻辑,%f
可能意味着将转换为 double
的 float
值。
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
(大写的ell)。%lf
在 printf
和 scanf
中都对应于 double
和 double *
参数... %f
之所以例外,是因为默认参数提升的原因,就像之前提到的那样。%Ld
并不意味着 long
。它意味着 未定义行为。short
类型的参数作为可变参数传递给printf
函数时,它会被提升为一个int
类型,而且%hd
格式化选项也存在。 - Basile Starynkevitchint
值超出范围时,它会以一种实现定义的方式转换回short
值。如果有%hf
,那么传递类似超出范围的double
值只能导致未定义的行为。 - autistic%hd
?尝试 short x = -72; printf( "%x\n", x );
并将结果与 short x = -72; printf("%hx\n", x);
进行比较。 - Andrew Henle6.5.2.2函数调用/ 6
和/ 7
中,讨论了表达式上下文中的函数调用(我强调):...
后面的任何float
参数都会转换为double
,并且printf
系列调用是以这种方式定义的(7.21.6.11
等)。int fprintf(FILE * restrict stream, const char * restrict format, ...);
printf()
系列调用实际接收浮点数,因此对其进行特殊的格式说明符(或修改符)几乎没有意义。%hf
将 double
值转换为 float
并以降低的精度显示它。顺便说一句,%hd
也有相同的含义(因为 printf
不接受 short
)。 - Basile Starynkevitchprintf
也没有接收到一个 short
! - Basile Starynkevitch%hd
的原因,当你将相同的评论复制/粘贴到我的答案部分时...这与此处提出的问题无关。请注意,通过“问题”我指的是“以问号结尾的一串单词”。 - autisticscanf
为float、double或long double都有不同的格式说明符,我不明白为什么printf
和类似函数没有采用相似的方式实现,但这就是C / C++及其标准所采用的方式。long double
的支持,现在将long double
与double
(64位/8字节)视为相同。他们本可以按需要填充到12或16字节边界,但却没有这样做。scanf
在接受 float
时需要一个指针参数。而调用 scanf
时不会发生相同的类型提升。 - skykinghh
是为了提供对所有新的stdint.h
类型的支持。这可能解释了为什么为小整数添加了长度修饰符,但没有为小浮点数添加。h
而没有hh
。就是这样简单,按照C90规定的语言并不总是一致的。后来版本也继承了这种不一致性。printf
)之前都会被转换为一种通用类型(即double
),所以printf
不需要区分浮点类型。double
更大,但可以更快地处理。其意图是,对于像a=b+c+d;
这样的表达式,将所有内容转换为80位类型,将三个80位数相加,然后将结果转换为64位类型,比将(b+c)作为64位类型计算,然后将其加上d
更快且更准确。long double
,实现可以引用新的80位类型或64位double
。不幸的是,尽管IEEE-754 80位类型的目的是使所有值自动升级到新类型,就像它们升级到double
一样,但ANSI使得新类型与其他浮点类型在传递给printf
或其他可变参数方法时有所不同,因此使得这种自动升级不可行。%f
格式说明符,但后来创建的long double
需要不同的%Lf
格式说明符(其中包含一个大写的L
)。%hhd
、%hd
、%ld
和%lld
被添加到printf
中,以使格式字符串更加一致,与scanf
相似,尽管它们对于printf
来说是多余的,因为默认参数提升已经实现了这个功能。
那么为什么没有为float
添加%hf
呢?很简单:看一下scanf
的行为,float
已经有了一个格式说明符,即%f
。而double
的格式说明符是%lf
。
C99正是将%lf
添加到printf
中的。在C99之前,%lf
的行为是未定义的(因为标准中没有任何定义)。从C99开始,它是%f
的同义词。
%Ld
也不代表long
。它的意思是未定义行为(与传递错误参数相同,会使你之前的%lf
->long double
对应关系变得未定义)。注意缺乏模式? - autisticscanf
说明符,因此下一个子句适用。关于“long float”,请参见C99 Rationale,第42页。猜测可能是决定ANSI委员会是否将显式的%lf
添加到printf
(在V7中它是未定义行为(通过省略)),以满足当时对“long float”的使用,与scanf
对齐,或者两者都适用。 - AntoineL