为什么不是`printf`中定义的`float`的说明符?

sky*_*ing 69 c floating-point printf rationale c-standard-library

它看起来像它可能是,有(至少在C99),可以适用于长度修改int:%hhd,%hd,%ld%lldsigned char,short,longlong long.甚至还有一个长度修饰符适用于double:%Lf均值long double.

问题是他们为什么省略float?按照模式,它可能是%hf.

Bas*_*tch 47

因为在C variadic函数调用中,任何float参数都被提升(即转换)为a double,所以printf获得a double并将用于va_arg(arglist, double)将其置于其实现中.

在过去(C89和K&R C),每个float参数都被转换为a double.当前标准省略了针对具有明确原型的固定arity函数的此促销.它与实现的ABI调用约定有关(并且详细说明).实际上,float当作为参数传递时,值通常会被加载到双浮点寄存器中,但细节可能会有所不同.以Linux x86-64 ABI规范为例.

此外,没有实际的理由给出一个特定的格式控制字符串,float因为你可以根据需要调整输出的宽度(例如%8.5f),并且比在中%hd更有用(几乎必要)scanfprintf

除此之外,我想原因(省略%hf指定float- double在调用者中指定printf)是历史性的:首先,C是一种系统编程语言,而不是HPC(Fortran在HPC中可能直到20世纪90年代后期)并且float不是很重要; 它(并且仍然)被认为short是一种降低内存消耗的方法.今天的FPU足够快(在台式机或服务器计算机上)以避免使用,float除非作为使用更少内存的手段.你基本上应该相信每个float地方(可能 FPU或CPU 内部)转换为double.

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

你可以花时间游说下一个ISO C标准得到%hf所接受printffloat(晋升为doubleprintf通话,就像short-s得到提升到int),与未定义行为时,双精度值超出界float-s和symetrically %hf所接受scanffloat指针.祝你好运.

  • 但这并不能解释`printf`的所有内容.还有所有窄类型的参数提升为`int`,但仍然有特殊的格式修饰符. (3认同)
  • [printf的h和hh修饰符的用途是什么?](http://stackoverflow.com/q/4586962/995714) (3认同)

Yu *_*Hao 15

因为默认参数促销.

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

C11§6.5.2.2函数调用

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

7函数原型声明符中的省略号表示法导致参数类型转换在最后声明的参数之后停止.默认参数提升是在尾随参数上执行的.

  • 但这并不能解释一切.还有所有窄类型的参数提升为`int`,但仍然有特殊的格式修饰符. (12认同)
  • 这句话也无关紧要,它只是描述了默认参数促销.人们不得不停止提升,因为有人引用标准的随机部分......实际上在你做之前阅读它所说的内容.相关的部分实际上表示默认参数提升适用于可变参数函数在6.5.2.2/7中进一步说明:"函数原型声明符中的省略号表示法导致参数类型转换在最后声明的参数之后停止.默认参数提升是在尾随参数上执行的." (7认同)
  • "问题是他们为什么省略浮动?" 您回答"因为默认参数促销",然后引用默认参数促销如何在没有函数原型的情况下工作.这对我来说相当随机. (5认同)
  • @Lundin我添加了6.5.2.2/7.第6节解释了如何在默认参数提升中将`float`提升为`double`,第7节解释了`...`如何执行默认参数提升.我之前的回答是不完整的,是的,它仍然是,但我不认为这是无关紧要/随机是公平的. (3认同)

aut*_*tic 10

由于调用可变参数函数时的默认参数提升,floatdouble在函数调用之前被隐式转换为,并且无法将float值传递给printf.由于无法传递floatprintf,因此不需要值的显式格式说明符float.

话虽如此,AntoineL提出了在评论一个有趣的问题%lf(目前用于scanf对应的参数类型double *)可能曾经代表" long float",这是在预C89天这类代名词,按照第42页的C99理由.按照这种逻辑,可能有意义的%f是,它意味着代表float已转换为a的值double.


关于hhh修饰符,%hhu%hu为这些格式说明符提供明确定义的用例:您可以打印大型unsigned intunsigned 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
Run Code Online (Sandbox Code Playgroud)

没有特别明确地限定转换intcharshort将导致的转换,但它至少是实现定义的,这意味着需要实现来实际记录此决策.

应该遵循这种模式%hf.

按照您观察到的模式,%hf应该将值转换float回范围之外float.然而,那种从收缩转换doublefloat 导致不确定的行为,以及有没有这样的事,作为一个unsigned float.你看到的模式没有意义.


要正式更正,%lf不表示long double参数,如果要传递long double参数,则会调用未定义的行为.从文档中明确指出:

l(ELL)...对在下一个无影响a,A,e,E,f,F,g,或G转换说明.

我很惊讶没有其他人接受过这个?%lf表示一个double参数,就像%f.如果要打印a long double,请使用%Lf(capital ell).

从此以后,由于前面提到的原因,因为默认参数促销,因此%lf对于两者而言,printf并且scanf对应于doubledouble *参数...... %f是特殊的.

......也%Ld不代表long.这意味着什么是未定义的行为.

  • 但是当``printf`和`%hd`存在`variadic参数时,`short`被提升为`int` (4认同)
  • 我的观点是,在你的论证之后,没有办法将`short`值传递给`printf`,但``hd``存在于'printf`中! (4认同)
  • @BasileStarynkevitch为什么`%hd`存在?试试`short x = -72; printf("%x \n",x);`并将结果与​​`short x = -72进行比较; printf("%hx \n",x);` (3认同)

pax*_*blo 5

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

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

7 /如果表示被调用函数的表达式具有包含原型的类型,则将参数隐式转换为相应参数的类型,就像通过赋值一样,将每个参数的类型作为不合格的版本它的声明类型.函数原型声明符中的省略号表示法导致参数类型转换在最后声明的参数之后停止.默认参数提升是在尾随参数上执行的.

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

int fprintf(FILE * restrict stream, const char * restrict format, ...);
Run Code Online (Sandbox Code Playgroud)

因此,由于printf()家庭调用无法实际接收浮点数,因此为它设置一个特殊的格式说明符(或修饰符)是没有意义的.

  • 但是`printf`也没有收到"短片"! (2认同)