为什么没有在`printf`中定义`float'的说明符?


69

它看起来像它可能是,有(至少在C99),可以适用于长度修改int%hhd%hd%ld%lld均值signed charshortlonglong long。甚至还有一个长度修饰符适用于double%Lf手段long double

问题是为什么他们忽略了float?按照该模式,可能是%hf


@Seb我从“错误”的地方得到了格式说明符。一些实施似乎接受%lflong double,标准但指定%Lf了点。我已经更新了问题。
2015年

...%Ld也不表示long。这意味着未定义的行为(与传递错误的参数相同的部分和段落使您以前的%lf->long double对应关系未定义)。注意缺少模式?
自闭症患者

@skyking那将是哪种实现?
自闭症2015年


1
@Seb:我(显然吗?)是关于scanf说明符的,因此下一条适用。关于long float,例如参见C99理由,第42页。什么猜测可能是决定ANSI委员会是否明确加入%lfprintf(这是不确定的行为(不作为)在V7),以与当时存在使用或者迎合long float,以与对齐scanf,或者同时适用。
AntoineL 2015年

Answers:


47

因为在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指定floatdouble-promoted printf是历史性的:起初,C是一种系统编程语言,而不是HPC语言(Fortran在HPC中可能是首选,直到1990年代后期),并且float不是很重要;它曾经(现在仍然)被认为short是一种降低内存消耗的方法。而且,今天的FPU足够快(在台式机或服务器计算机上)可以避免使用,float除非这样做是为了减少内存。基本上,您应该相信每一个都已转换float为的某个位置(也许FPU或CPU内部double

实际上,您的问题可能被解释为:为什么%hd存在printf(它基本上是无用的,因为当您通过它时printf会得到一个;但是需要它!)。我不知道为什么,但是我想比在系统编程中更有用。intshortscanf

您可能需要花一些时间游说下一个ISO C标准,以%hfprintffor接受floatdoubleprintf调用时被提升为,short-s则被提升为int),并且在双精度值超出float-s的范围时具有不确定的行为,%hf并被scanffor对称地接受float指针。祝你好运。


3
但是,这并不能解释所有内容printf。还可以将所有窄类型的参数提升为int,但是仍然有针对它们的特殊格式修饰符。
詹斯·古斯特

1
但是对于小整数类型格式说明符,C标准解决了提升问题。例如见7.19.6.1 fprintf中: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);。因此,我认为这与隐式类型提升没有任何关系。
伦丁2015年

@Lundin这是一个极好的观察!消除这个答案的最后一点疑问就足够了。
anatolyg's

1
关于“没有实际的理由为”提供特定的格式控制字符串float。在极端情况下,以某种不同的方式"%ha"显示float对齐可能会有用,printf("%a %ha\n", FLT_MAX, FLT_MAX) --> 0x1.fffffep+127 0x0.ffffffp+128因为e第一种情况下的讨厌问题只有3个有效位。
chux-恢复莫妮卡2015年


15

由于默认参数提升

printf()是变量自变量函数(...在其签名中),所有自float变量都提升为double

C11§6.5.2.2函数调用

6如果表示被调用函数的表达式的类型不包含原型,则对每个自变量执行整数提升,并将具有该类型的自变量float提升为double。这些称为默认参数提升。

7函数原型声明器中的省略号引起参数类型转换在最后声明的参数之后停止。默认参数提升是对尾随参数执行的。


12
但是,这并不能解释所有情况。还可以将所有窄类型的参数提升为int,但是仍然有针对它们的特殊格式修饰符。
Jens Gustedt

7
此引号也无关紧要,它仅描述默认的参数提升。人们不得不停止投票只是因为有人引用了标准的随机部分……实际上是在阅读标准之前先阅读一下。相关的部分,实际上说默认参数提升适用于可变参数函数,位于6.5.2.2/7的更下方:“函数原型声明器中的省略号引起参数类型转换在最后声明的参数之后停止。默认参数提升是对尾随参数执行的。”
伦丁2015年

3
@Lundin我添加了6.5.2.2/7。第6节说明了如何floatdouble默认参数提升为,第7节说明了如何...执行默认参数提升。我以前的回答是不完整的,是的,而且仍然是,但是我不认为这无关紧要/随机是公平的。
于昊

5
“问题是为什么他们会忽略浮动?” 您回答了“因为默认参数提升”,然后引述了在没有函数原型的情况下默认参数提升的工作方式。这对我来说是随机的。
伦丁2015年

1
@chux问题仍然是为什么没有人对双重论点做同样的事情。
Random832

10

由于在调用可变参数函数时默认的参数提升,因此将float值隐式转换为double函数调用之前的值,并且无法将float值传递给printf。由于无法将float值传递给printf,因此无需为float值使用明确的格式说明符。

话虽如此,AntoineL提出了一个有趣的点的评价是%lf(目前在使用scanf对应于一个参数类型double *)可能曾经代表“ long float”,这是在预C89天这类代名词,按照第42页的C99基本原理。按照这种逻辑,%f原本打算代表float已转换为的值可能是有道理的double


关于hhh长度修饰符,%hhu%hu为这些格式说明符提供了定义明确的用例:您可以打印大写unsigned intunsigned short不带强制转换的最低有效字节,例如:

转换intcharshort将导致的缩小转换的定义不是很明确,但至少是实现定义的,这意味着需要实现才能实际记录此决策。

按照模式应该是%hf

按照您观察到的模式,%hf应将值超出floatback的范围转换为float。但是,这种从double到的狭窄转换会float 导致未定义的行为,并且没有这样的东西unsigned float。您看到的模式没有意义。


从形式上正确来说,%lf它并不表示long double参数,如果要传递long double参数,则会调用未定义的行为。从文档中可以明显看出:

l(ELL)...具有在以下没有影响aAeEfFg,或G转换说明。

我很惊讶没有其他人对此有所了解?%lf表示一个double参数,就像%f。如果要打印long double,请使用%Lf(大写)。

从此以后,%lf对于printfscanf对应于doubledouble *参数都应该有意义。%f都是例外,这仅是由于默认参数提升所致,原因如前所述。

...%Ld也不表示long。这意味着未定义的行为


4
short被提升到int 一个可变参数参数使用时printf%hd存在
巴西莱Starynkevitch

@BasileStarynkevitch OP的问题是“他们为什么省略float?”而不是“他们为什么不省略short?”。您的担心对我来说似乎无关紧要,但是如果您真的希望我这样做,请吠叫,我会解释为什么会这样。
自闭症患者

4
我的观点是,按照您的论点,无法将short值传递给printf,但是%hd存在printf
Basile Starynkevitch 2015年

@BasileStarynkevitch怎么样?
自闭症患者

3
@BasileStarynkevitch为什么%hd存在?尝试short x = -72; printf( "%x\n", x );将结果与short x = -72; printf("%hx\n", x);
Andrew Henle

5

从ISO C11标准6.5.2.2 Function calls /6和中/7,在表达式的上下文中讨论函数调用(我的重点是):

6 /如果表示被调用函数的表达式的类型不包括原型,则对每个参数执行整数提升,并将具有float类型的参数提升为双精度。这些称为默认参数提升。

7 /如果表示被调用函数的表达式的类型确实包含原型,则将参数隐式转换为相应参数的类型,就像通过赋值一样,将每个参数的类型视为的非限定版本。其声明的类型。函数原型声明器中的省略号引起参数类型转换在最后声明的参数之后停止。默认参数提升是对尾随参数执行的。

这意味着原型中的所有float参数...都将转换为,double并以printf这种方式定义了调用族(7.21.6.11et seq):

因此,由于printf()-family调用实际上无法接收浮点数,因此为其指定特殊格式说明符(或修饰符)几乎没有意义。


在标准中,我们可能会有类似的东西:%hfdouble值转换为afloat并以降低的精度显示它。BTW,%hd具有等效意义(因为printf没有一个得到short既不)
巴西莱Starynkevitch

2
但是printf没有收到short既不!
Basile Starynkevitch 2015年

@BasileStarynkevitch您正在播放唱片。%hd当您将相同的注释复制/粘贴到本节中作为我的答案时,我已经解释了背后的原因...与此处提出的问题无关。请注意,问题我的意思是“以问号结尾的单词字符串”。
自闭症2015年

2

阅读fscanf下面的C基本原理,可以找到以下内容:

C99的新功能:在C99中添加了hh和ll长度修饰符。ll支持新的long long int类型。hh增加了将字符类型与所有其他整数类型相同的功能;这对于实现SCNd8之类的宏很有用(请参阅7.18)。

因此,据说hh添加的目的是为所有新stdint.h类型提供支持。这可以解释为什么为小整数而不是小浮点添加了长度修饰符。

它没有解释为什么C90不一致,h但是没有hh。C90中指定的语言并不总是一致的,就这么简单。后来的版本继承了不一致的地方。


2

发明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)。


2

%hhd%hd%ld以及%lld加入printf,使格式字符串以更一致的scanf,即使他们是多余的printf,因为默认参数提升。

那么为什么不%hf添加float呢?这很容易:查看scanf的行为,float已经有了格式说明符。是%f。与格式说明的double%lf

%lf正是C99添加的内容printf。在C99之前,的行为%lf是不确定的(通过省略标准中的任何定义)。从C99开始,它是的同义词%f


2

考虑到scanf对于float,double或long double具有单独的格式说明符,我不明白为什么printf和相似的函数没有以类似的方式实现,但这就是C / C ++和标准最终的实现方式。

推送或弹出操作的最小大小可能会出现问题,具体取决于处理器和当前模式,但这可以通过默认填充来解决,这类似于局部变量或结构中变量的默认对齐方式。long double从16位编译器到32/64位编译器时,Microsoft放弃了对80位(10字节)的支持,现在将long doubles与doubles(64位/ 8字节)相同。他们可以根据需要将其填充到12或16个字节的边界,但这并未完成。


1
区别在于,scanf接受时需要一个指针参数float。呼叫时不会发生同一类型的提升scanf
2015年

@skyking-我理解,但是最初可以指定类型升级来处理更多情况,并且已经定义了scanf的格式说明符。
rcgldr

没有什么可以在指针上进行类型提升的事情(可能不是将其提升为远距离指针的事情)。更改目标的类型不是一个好主意,首先,这意味着在取消对16位数据的指针的引用时,如果它是32位,则可能会导致缓冲区溢出。此外,如果您具有中间字节序,则也无法采用其他方法。
2015年

@skyking-我的意思是针对诸如printf之类的函数的参数进行类型升级。scanf要求每种目标类型都有特定的格式说明符。
rcgldr
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.