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

它看起来可能是,至less在C99中可以应用于int长度修饰符: %hhd%hd%ld%lld表示有signed charshortlonglong 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 ),并且%hdscanf比在printf

    除此之外, 我猜想,原因 (省略%hf指定floatprintf中调用double是历史性的 :起初,C是一种系统编程语言,而不是HPC(Fortran在HPC中首选, 20世纪90年代后期)和float并不是很重要; 它是(现在还是)被认为是short ,一种降低内存消耗的方法。 而今天的FPU足够快(在桌面计算机或服务器计算机上),以避免使用float除非使用更less的内存。 你基本上应该相信每一个float都是在某处(也许 FPU或CPU 内部 )转换成double

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

    你可以花时间游说下一个ISO C标准来获得由printffloat %hf (在printf调用时提升为double ,如short -s被提升为int ),当double精度值超出限制float -s,以及对于float指针, scanf接受的对称%hf 。 祝你好运。

    由于默认参数促销

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

    C11§6.5.2.2函数调用

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

    7函数原型声明符中的省略号表示法导致参数types转换在最后声明的参数后停止。 在结尾参数上执行默认参数促销。

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

    话虽如此, AntoineL在一个评论中提出了一个有趣的观点: %lf (目前在scanf用于对应于一个参数typesdouble * )可能曾经代表“ long float ”,这是C89之前的一个同义词types,根据C99基本原理的第42页。 通过这个逻辑, %f可能是有意义的,代表一个已经转化为doublefloat值。


    关于hhh长度修饰符, %hhu%hu为这些格式说明符提供了一个定义明确的用例:您可以在不进行%hhu的情况下打印大型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 

    它没有特别明确地定义从intcharshort转换是否会缩小,但至less是实现定义的 ,这意味着实现需要实际logging这个决定。

    遵循这个模式,应该是%hf

    遵循你所观察到的模式, %hf应该将float范围之外的值转换回float 。 然而,这种从doublefloat的缩小转换会导致未定义的行为 ,并且不存在无unsigned float 。 你看到的模式没有意义。


    为了forms上正确, %lf并不表示一个long double参数,如果你传递一个long double参数, 你将会调用未定义的行为 。 从文档中明确指出:

    l (ell)…对后面的aeEfFgG转换说明符没有影响。

    我很惊讶没有人接受这个? %lf表示double参数,就像%f 。 如果你想打印一个long double ,使用%Lf (大写)。

    从此以后应该明白,对于printfscanf%lf对应于doubledouble *参数。 %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.htypes而添加的。 这可以解释为什么一个长度修饰符被添加为小整数,但不是小浮点数。

    这并不能解释为什么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个字节的边界,但是这没有完成。