为什么不是`printf`中定义的`float`的说明符?
它看起来可能是,至less在C99中可以应用于int
长度修饰符: %hhd
, %hd
, %ld
和%lld
表示有signed char
, short
, long
和long long
。 甚至还有一个适用于double
的长度修饰符: %Lf
表示long double
%Lf
。
问题是为什么他们忽略float
? 遵循这个模式,它可能是%hf
。
因为在C variadic函数调用中,任何float
参数都被提升(即转换)为double
,所以printf
得到一个double
,并使用va_arg(arglist, double)
来实现它。
在过去(C89和K&R C),每个float
参数都被转换为double
float
。 目前的标准对具有明确原型的固定function函数省略了这种提升。 它涉及到ABI和执行的调用约定 (详细情况将在后面解释)。 实际上, float
值在作为parameter passing时通常会加载到双浮点寄存器中,但细节可能会有所不同。 以Linux x86-64 ABI规范为例。
而且,没有实际的理由给float
的特定格式控制string,因为你可以调整输出的宽度(例如%8.5f
),并且%hd
在scanf
比在printf
除此之外, 我猜想,原因 (省略%hf
指定float
在printf
中调用double
) 是历史性的 :起初,C是一种系统编程语言,而不是HPC(Fortran在HPC中首选, 20世纪90年代后期)和float
并不是很重要; 它是(现在还是)被认为是short
,一种降低内存消耗的方法。 而今天的FPU足够快(在桌面计算机或服务器计算机上),以避免使用float
除非使用更less的内存。 你基本上应该相信每一个float
都是在某处(也许在 FPU或CPU 内部 )转换成double
。
实际上,你的问题可能被解释为:为什么%hd
存在于printf
(基本上没用,因为printf
在传递给它的时候会得到一个int
;但是scanf
需要它!)。 我不知道为什么,但我想比在系统编程中可能更有用。
你可以花时间游说下一个ISO C标准来获得由printf
为float
%hf
(在printf
调用时提升为double
,如short
-s被提升为int
),当double精度值超出限制float
-s,以及对于float
指针, scanf
接受的对称%hf
。 祝你好运。
由于默认参数促销 。
printf()
是一个variables参数函数( ...
在其签名中),所有float
参数都被提升为double
。
C11§6.5.2.2函数调用
6如果表示被调用函数的expression式的types不包含原型,则将对每个参数执行整数提升,并将具有
float
types的参数提升为double
。 这些被称为默认参数促销。7函数原型声明符中的省略号表示法导致参数types转换在最后声明的参数后停止。 在结尾参数上执行默认参数促销。
由于调用可变参数函数时的默认参数提升,在函数调用之前, float
值被隐式转换为double
,并且无法将float
值传递给printf
。 由于无法将float
值传递给printf
,因此不需要为float
值指定明确的格式说明符。
话虽如此, AntoineL在一个评论中提出了一个有趣的观点: %lf
(目前在scanf
用于对应于一个参数typesdouble *
)可能曾经代表“ long float
”,这是C89之前的一个同义词types,根据C99基本原理的第42页。 通过这个逻辑, %f
可能是有意义的,代表一个已经转化为double
的float
值。
关于hh
和h
长度修饰符, %hhu
和%hu
为这些格式说明符提供了一个定义明确的用例:您可以在不进行%hhu
的情况下打印大型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
转换是否会缩小,但至less是实现定义的 ,这意味着实现需要实际logging这个决定。
遵循这个模式,应该是
%hf
。
遵循你所观察到的模式, %hf
应该将float
范围之外的值转换回float
。 然而,这种从double
到float
的缩小转换会导致未定义的行为 ,并且不存在无unsigned float
。 你看到的模式没有意义。
为了forms上正确, %lf
并不表示一个long double
参数,如果你传递一个long double
参数, 你将会调用未定义的行为 。 从文档中明确指出:
l
(ell)…对后面的a
,e
,E
,f
,F
,g
或G
转换说明符没有影响。
我很惊讶没有人接受这个? %lf
表示double
参数,就像%f
。 如果你想打印一个long double
,使用%Lf
(大写)。
从此以后应该明白,对于printf
和scanf
, %lf
对应于double
和double *
参数。 %f
仅仅是因为缺省参数促销才是例外,因为前面提到的原因。
… %Ld
也不意味着long
。 这意味着什么是未定义的行为 。
从ISO C11标准6.5.2.2 Function calls /6
和/7
,在expression式的上下文中讨论函数调用(我强调):
6 /如果表示被调用函数的expression式的types不包含原型,则将对每个参数执行整数提升,将types为float的参数提升为double。 这些被称为默认参数促销。
7 /如果表示被调用函数的expression式包含一个包含原型的types,那么这些参数就像通过赋值一样被隐式地转换为相应参数的types,并将每个参数的types作为非限定版本其声明的types。 函数原型声明符中的省略号表示法导致参数types转换在最后声明的参数后停止。 在结尾参数上执行默认参数促销。
这意味着原型中的...
之后的任何float
参数都被转换为double
并且调用printf
家族的方式( 7.21.6.11
et seq):
int fprintf(FILE * restrict stream, const char * restrict format, ...);
所以,因为printf()
家庭调用没有办法实际接收一个float,所以对它有一个特殊的格式说明符(或修饰符)是没有意义的。
阅读C的基本原理,在fscanf下面可以find:
C99的新function:在C99中添加了hh和ll长度修饰符。 会支持新的long long inttypes。 hh增加了将字符types与所有其他整数types相同的function; 这可以用来实现macros中的SCNd8(见7.18)。
所以应该是为了支持所有新的stdint.h
types而添加的。 这可以解释为什么一个长度修饰符被添加为小整数,但不是小浮点数。
这并不能解释为什么C90不一致,但没有。 C90中指定的语言并不总是一致的,就像那样简单。 后来的版本inheritance了不一致。
当C被发明的时候,所有的浮点值在被用于计算或者被传递给函数(包括) printf
之前被转换为一个通用的types(即double
),所以printf
不需要在浮点types。
为了提高算术效率和准确性,IEEE-754浮点标准定义了一个80位的types,它比普通的64位double
精度要大,但是处理速度更快。 其意图是给定一个expression式如a=b+c+d;
将一切转换为80位types,将三个80位数相加,并将结果转换为64位types,比将(b+c)
和(b+c)
为64位types,然后将其添加到d
。
为了支持这种新types,ANSI C定义了一个新types的long double
,其实现可以指新的80位types或64位double
。 不幸的是,尽pipeIEEE-754 80位types的目的是让所有的值自动提升到新的types,但它们已经被提升了double
,ANSI使得新types被传递给printf
或其他可变参数与其他浮点types不同,从而使这种自动升级变得站不住脚。
因此,创buildC时存在的浮点types都可以使用相同的%f
格式说明符,但之后创build的long double
格式要求使用不同的%Lf
格式说明符(使用大写 L
)。
%hhd
, %hd
, %ld
和%lld
被添加到printf
,使得格式string与scanf
更加一致,即使它们对于printf
是冗余的,因为缺省的参数促销。
那么为什么没有%hf
添加为float
? 这很简单:查看scanf
的行为, float
已经有一个格式说明符。 这是%f
。 而double
的格式说明符是%lf
。
这%lf
正是C99添加到printf
。 在C99之前, %lf
的行为是未定义的(通过省略标准中的任何定义)。 从C99开始,它就是%f
的同义词。
考虑到scanf
对float,double或long double有单独的格式说明符,我不明白为什么printf
和类似的函数没有以类似的方式实现,但这就是C / C ++和标准的结局。
根据处理器和当前模式,推送或popup操作的最小大小可能存在问题,但是可以使用默认填充处理,类似于结构中局部variables或variables的默认alignment。 当从16位编码器到32位/ 64位编译器,微软支持80位(10字节) long double
s,现在处理long double
s和double
s(64 bit / 8 byte)。 根据需要,它们可以填充到12或16个字节的边界,但是这没有完成。