h和hh修饰符用于printf的目的是什么?
除了%hn
和%hhn
(其中h
或hh
指定指向对象的大小), printf
格式说明符的h
和hh
修饰符有什么意义?
由于标准要求的可变参数函数的默认升级,不可能将char
或short
types的参数(或其任何有符号/无符号变体)传递给printf
。
根据7.19.6.1(7), h
修饰符:
指定后面的d,i,o,u,x或X转换规则适用于short int或unsigned short int参数(参数将根据整数提升进行提升,但其值将转换为short int或打印之前无符号短整型); 或者一个下面的n转换规范适用于一个短int参数的指针。
如果参数实际上是short
types或unsigned short
types,那么将int
提升为int
,然后将其转换为short
或unsigned short
将会产生与提升为int
相同的值 ,而不会返回任何转换。 因此,对于types为short
或unsigned short
, %d
, %u
等应该给%hd
, %hu
等等(对于char
types和hh
同样)。
据我所知, h
或hh
修饰符可能有用的唯一情况是当参数在short
或unsigned short
范围外传递一个int
,例如
printf("%hu", 0x10000);
但我的理解是,传递这样的错误types会导致不确定的行为,所以你不能期望它打印0。
我见过的一个真实世界的例子是这样的代码:
char c = 0xf0; printf("%hhx", c);
尽pipe实现了一个简单的char
types(在这种情况下, printf("%x", c)
会打印fffffff0
或类似的东西),作者期望它打印f0
。 但是这个期望值得吗?
(注意:原来的types是char
,它被提升为int
并被转换回unsigned char
而不是char
,从而改变了被打印的值。但是标准是否指定了这个行为,或者它是一个实现细节破碎的软件可能依靠?)
一个可能的原因:在格式化input函数中使用这些修饰符的对称性? 我知道这不是绝对必要的,但也许有价值的东西呢?
虽然他们没有提到C99基本原理文档中的“h”和“hh”修饰符的对称性的重要性,但委员会确实提到它是为了考虑为什么fscanf()
支持“%p”转换说明符即使这对于C99来说并不是新鲜事物 – “%p”支持在C90中):
使用%p的input指针转换被添加到C89,虽然它显然是有风险的,因为与fprintf的对称性。
在fprintf()
的章节中,C99的基本原理文件确实讨论了“hh”被添加了,但是只是把读者引用到fscanf()
部分:
在C99中添加了%hh和%ll长度修饰符(参见§7.19.6.2)。
我知道这是一个微不足道的线索,但无论如何我都在猜测,所以我想我会提出任何可能的论点。
此外,为了完整性,“h”修饰符是在原来的C89标准 – 大概是在那里,即使它不是严格必要的,因为广泛的现有用途,即使可能没有技术要求使用修饰符。
在%...x
模式下,所有值都被解释为无符号。 因此负数将被打印为无符号转换。 在大多数处理器使用的二进制补码algorithm中,在有符号负数和它的正无符号等价物之间的比特模式没有差别,这是由模数algorithm定义的(将场的最大值加上一个负数,根据到C99标准)。 许多软件(特别是最有可能使用%x
的debugging代码)使得无声的假设是:有符号负值的位表示和无符号强制转换是相同的,这在2的补码机器上才是真实的。
这种转换的机制是这样的:hex值的表示总是暗示,可能不准确的是,一个数字已经以二进制补码表示,只要它不碰到不同整数表示具有不同范围的边缘条件。 这甚至适用于算术表示,其中值0不是用全0的二进制模式表示的。
因此,在hex中显示为unsigned long
整数的负short
将在任何机器上填充f
,这是由于在促销中隐式的符号扩展, printf
将打印。 价值是一样的,但是在视觉上误导了场的大小,意味着大量的范围,根本不存在。
%hx
截断显示的表示,以避免这种填充,就像您从现实世界的用例中得出的结论一样。
printf
的行为在short
应该被打印为short
的范围之外传递一个int
时是不确定的,但是最简单的实现到目前为止只是通过原始的downcast丢弃高位,所以虽然spec 不需要任何特定的行为,几乎任何理智的实现将只执行截断。 不过,通常有更好的方法来做到这一点。
如果printf不是填充值或显示带符号值的无符号表示, %h
不是很有用。
我能想到的唯一的用途是传递一个unsigned short
或unsigned char
并使用%x
转换说明符。 你不能简单地使用纯粹的%x
– 值可能被提升为int
而不是unsigned int
,然后你有未定义的行为。
你的select要么明确地把参数转换为unsigned
; 或者使用%hx
/ %hhx
作为参数。
printf()
等的可变参数会使用默认转换自动提升,所以在传递给函数时,任何short
或char
值都会被提升为int
。
在缺lessh
或hh
修饰符的情况下,您将不得不掩盖传递的值以可靠地获取正确的行为。 使用修饰符,您不再需要掩饰值; printf()
实现正确地完成了这项工作。
具体来说,对于格式%hx
, printf()
的代码可以执行如下操作:
va_list args; va_start(args, format); ... int i = va_arg(args, int); unsigned short s = (unsigned short)i; ...print s correctly, as 4 hex digits maximum ...even on a machine with 64-bit `int`!
我很乐意假设short
是一个16位的数量; 当然这个标准实际上并不能保证。
我同意你的观点,这并不是绝对必要的,所以仅仅因为这个原因,在C库函数中是不行的:)
这对于不同标志的对称性可能是“好的”,但是由于它隐藏了“转换为int
”规则,所以它大部分适得其反。