它看起来像它可能是,有(至少在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
。
scanf
说明符的,因此下一条适用。关于long float
,例如参见C99理由,第42页。什么是猜测可能是决定ANSI委员会是否明确加入%lf
到printf
(这是不确定的行为(不作为)在V7),以与当时存在使用或者迎合long float
,以与对齐scanf
,或者同时适用。
Answers:
因为在C可变参数函数调用中,任何float
参数都被提升(即转换)为a double
,所以printf
获取adouble
并使用va_arg(arglist, double)
其用于在其实现中获取它。
在过去(C89和K&R C),每个float
参数都转换为double
。当前标准省略了对具有明确原型的固定Arity函数的推广。它与实现的ABI和调用约定有关(并在中进行了详细说明)。实际上,将float
值作为参数传递时通常会将其加载到双浮点寄存器中,但是细节可能会有所不同。阅读Linux x86-64 ABI规范作为示例。
此外,float
由于您可以根据需要调整输出的宽度(例如,使用%8.5f
),因此没有实际的理由来指定特定的格式控制字符串,并且%hd
在其中scanf
比在中更有用(几乎是必需的)。printf
除此之外,我猜想原因(省略在caller-in中%hf
指定float
为double
-promoted printf
)是历史性的:起初,C是一种系统编程语言,而不是HPC语言(Fortran在HPC中可能是首选,直到1990年代后期),并且float
不是很重要;它曾经(现在仍然)被认为short
是一种降低内存消耗的方法。而且,今天的FPU足够快(在台式机或服务器计算机上)可以避免使用,float
除非这样做是为了减少内存。基本上,您应该相信每一个都已转换float
为的某个位置(也许在FPU或CPU内部)double
。
实际上,您的问题可能被解释为:为什么%hd
存在printf
(它基本上是无用的,因为当您通过它时printf
会得到一个;但是需要它!)。我不知道为什么,但是我想比在系统编程中更有用。int
short
scanf
您可能需要花一些时间游说下一个ISO C标准,以%hf
被printf
for接受float
(double
在printf
调用时被提升为,short
-s则被提升为int
),并且在双精度值超出float
-s的范围时具有不确定的行为,%hf
并被scanf
for对称地接受float
指针。祝你好运。
printf
。还可以将所有窄类型的参数提升为int
,但是仍然有针对它们的特殊格式修饰符。
hh Specifies that a following d, i, o, u, x, or X conversion specifier applies to a signed char or unsigned char argument (the argument will have been promoted according to the integer promotions, but its value shall be converted to signed char or unsigned char before printing);
。因此,我认为这与隐式类型提升没有任何关系。
float
。在极端情况下,以某种不同的方式"%ha"
显示float
对齐可能会有用,printf("%a %ha\n", FLT_MAX, FLT_MAX) --> 0x1.fffffep+127 0x0.ffffffp+128
因为e
第一种情况下的讨厌问题只有3个有效位。
由于默认参数提升。
printf()
是变量自变量函数(...
在其签名中),所有自float
变量都提升为double
。
C11§6.5.2.2函数调用
6如果表示被调用函数的表达式的类型不包含原型,则对每个自变量执行整数提升,并将具有该类型的自变量
float
提升为double
。这些称为默认参数提升。7函数原型声明器中的省略号引起参数类型转换在最后声明的参数之后停止。默认参数提升是对尾随参数执行的。
int
,但是仍然有针对它们的特殊格式修饰符。
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
back的范围转换为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
。这意味着未定义的行为。
short
被提升到int
一个可变参数参数使用时printf
和%hd
存在
float
?”而不是“他们为什么不省略short
?”。您的担心对我来说似乎无关紧要,但是如果您真的希望我这样做,请吠叫,我会解释为什么会这样。
short
值传递给printf
,但是%hd
存在printf
!
%hd
存在?尝试short x = -72; printf( "%x\n", x );
将结果与short x = -72; printf("%hx\n", x);
从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()
-family调用实际上无法接收浮点数,因此为其指定特殊格式说明符(或修饰符)几乎没有意义。
%hf
将double
值转换为afloat
并以降低的精度显示它。BTW,%hd
具有等效意义(因为printf
没有一个得到short
既不)
printf
没有收到short
既不!
%hd
当您将相同的注释复制/粘贴到本节中作为我的答案时,我已经解释了背后的原因...与此处提出的问题无关。请注意,问题我的意思是“以问号结尾的单词字符串”。
发明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
添加float
呢?这很容易:查看scanf
的行为,float
已经有了格式说明符。是%f
。与格式说明的double
是%lf
。
这%lf
正是C99添加的内容printf
。在C99之前,的行为%lf
是不确定的(通过省略标准中的任何定义)。从C99开始,它是的同义词%f
。
考虑到scanf
对于float,double或long double具有单独的格式说明符,我不明白为什么printf
和相似的函数没有以类似的方式实现,但这就是C / C ++和标准最终的实现方式。
推送或弹出操作的最小大小可能会出现问题,具体取决于处理器和当前模式,但这可以通过默认填充来解决,这类似于局部变量或结构中变量的默认对齐方式。long double
从16位编译器到32/64位编译器时,Microsoft放弃了对80位(10字节)的支持,现在将long double
s与double
s(64位/ 8字节)相同。他们可以根据需要将其填充到12或16个字节的边界,但这并未完成。
scanf
接受时需要一个指针参数float
。呼叫时不会发生同一类型的提升scanf
。
%lf
了long double
,标准但指定%Lf
了点。我已经更新了问题。