C 语言中曾被我误解的 printf 格式
C 语言的 printf 函数的第一个参数是格式字符串。其中有 %d、%hhd、%hd、%ld、%f 等关于不同长度的整型和浮点型数据的形式。
我一开始以为,%d 严格对应 int,%hhd 严格对应 char,%hd 严格对应 short,%ld 严格对应 long,%f 严格对应 double。当然,这是臆断。
昨天在看《C 程序设计语言》(K&R)的时候,突然发现,书里面说,用 float 来表示浮点数,而在 printf 中,用的是一个 (5.0 / 9.0) * (fahr – 32.0) 这样一个式子,其中 fahr 是一个 float 型数据。我于是起疑心了。我记得以前学 C 语言的时候说,5.0 这种表示方法表示的是 double 型的数。那么,为什么他们不说,要用 double 型来表示传向 printf 的值呢?
我突然觉得这个是个大问题!万一误把 float 型的值传给了 printf,而 float 是以 IEEE754 的单精度型的表示方法表示的,double 则是以 IEEE754 的双精度型表示方法来表示的。根据 IEEE754 的标准,两种表示方法在二进制的位数上相差很大,岂不是要让 printf 显示出无用的乱数来了?
今天,我上网查询了 C 的 ISO 标准,WG14/N1124 ISO/IEC 9899:TC2 委员会草案。里面是说,%f 是要接受 double 型的参数,并且没有对应的接受 float 型参数的表示。
我又做了一个实验,验证就算 float 型参数传给 printf 的 %f 位置,也不会混乱,而是正确显示。并且,同样值的 float 型数据和 double 型数据的二进制表示相差很大。
后来,我又在 Borland C++ Builder 帮助文件中查了 va_arg 的用法,才终于明白过来:原来在 C 语言中的可变参数作为形参的情况中,如果实际参数是小数据类型,必须先扩展成为标准的数据类型之后才能使用。转换表如下:
char => int
unsigned char => int
signed char => int
float => double
那么在标准中有没有这样的转换呢?查了一下发现:
6.5.2.2 Function calls
6 If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions. …
注意上面的“the default argument promotions”。接下来:
7 … The ellipsis notation in a function prototype declarator causes argument type conversion to stop after the last declared parameter. The default argument promotion are performed on trailing arguments.
也就是说,对于 … 这样的可变长参数列表,默认的转换是会发生的。然后为了搞得更清楚一些,关于 integer promotions,发现在 6.3.1.1 Boolean, characters, and integers 中有:
2 If an int can represent all values of the original type, the value is converted to an int; otherwise, it is converted to an unsigned int. These are called the integer promotions. All other types are unchanged by the integer promotions.
结论:在 printf 中使用 %f 的确对应的是 double,并且当 float 型数据作为参数传入的时候,也会被转换成 double。另外,当 char 型、short 型参数被传入的时候,也会被转换成 int。
好啊。果然容易忽视,不过用起来蛮方便的。还有那个__VA_ARG__还是应该广泛试验一下。dede唱的独脚戏真是学的惟妙惟肖,声音也好听。