printf的H和HH修饰符的目的是什么?
-
14-10-2019 - |
题
除了 %hn
和 %hhn
(在哪里 h
或者 hh
指定大小 指向 对象),什么是 h
和 hh
修饰符 printf
格式指定符?
由于默认促销措施,标准需要用于变异功能,因此不可能通过类型的参数 char
或者 short
(或其任何签名/未签名的变体) printf
.
根据7.19.6.1(7), h
修饰符:
指定以下d,i,o,u,x或x转换指定的特定版本适用于简短的int或unsigned简短int参数(该论点将根据整数促销促进,但其价值应转换为简短的int int int int或打印前未签名的简短int);或以下n转换规格适用于指向简短的INT参数。
如果论点实际上是类型的 short
或者 unsigned short
, ,然后晋升为 int
然后转换回 short
或者 unsigned short
会产生相同的 价值 作为晋升 int
没有任何转换。因此,对于类型的参数 short
或者 unsigned short
, %d
, %u
, 等等,应该给出相同的结果 %hd
, %hu
, 等等(同样 char
类型和 hh
).
据我所知,唯一的情况 h
或者 hh
修饰符可能很有用,是当参数传递给它 int
范围之外 short
或者 unsigned short
, ,例如
printf("%hu", 0x10000);
但是我的理解是,无论如何,通过这种错误的类型导致了不确定的行为,因此您不能指望它打印0。
我看到的一个现实世界案件是这样的代码:
char c = 0xf0;
printf("%hhx", c);
作者期望它打印的地方 f0
尽管实施具有平淡 char
签名的类型(在这种情况下, printf("%x", c)
会打印 fffffff0
或类似)。但是这种期望值得吗?
(注意:发生的是原始类型是 char
, ,被提升为 int
并转换回 unsigned char
代替 char
, ,因此更改打印的值。但是标准是否指定了此行为,还是破碎的软件可能依赖的实现细节?)
解决方案
一个可能的原因:在格式化输入函数中使用这些修饰符的对称性?我知道这不是严格必要的,但是也许有价值?
尽管他们没有提及对称性对“ H”和“ HH”修饰符的重要性 C99理由文件, ,委员会确实提到了这一点,以考虑为什么支持“%p”转换指定符 fscanf()
(即使对于C99-“%p”支持并不是什么新鲜事物),在C90中):
对于与FPRINTF的对称性,尽管它显然有风险,但使用%P的输入指针转换添加到C89中。
在有关的部分 fprintf()
, ,C99理由文件确实讨论了“ HH”,但仅将读者引用到 fscanf()
部分:
在C99中添加了%HH和%LL长度修饰符(请参阅第7.19.6.2节)。
我知道这是一个脆弱的线程,但无论如何我都在猜测,所以我想我会提出任何可能的论点。
另外,为了完整性,“ H”修饰符是在原始C89标准中 - 大概是在存在的情况下,即使不是严格必要的,即使存在广泛的现有用途,也可能没有使用修饰符的技术要求。
其他提示
在 %...x
模式,所有值都被解释为未签名。因此,负数被打印为其未签名的转换。在大多数处理器使用的2的补体算术中,签名的负数及其正面的无符号等价物之间的位模式没有差异,这是由模量算术定义的(将字段的最大值加上一个负数添加到负数,根据到C99标准)。大量软件 - 尤其是最有可能使用的调试代码 %x
- 使无声的假设是,签名的负值的位表示及其未签名的铸件是相同的,这仅在2的补体机器上是正确的。
这种铸件的机制使得价值的六边形表示总是暗示,可能是不准确的,只要它没有达到不同整数表示的范围不同的边缘条件,就可以在2的补体中渲染一个数字。对于算术表示,该值0并未用所有0的二进制模式表示,这甚至是正确的。
负面 short
显示为 unsigned long
因此 f
, ,由于促销中的隐式符号扩展, printf
会打印。这 价值 是一样的,但在视觉上对田地的大小确实具有误导性,这意味着根本不存在的大量范围。
%hx
截断显示的表示形式以避免使用此填充,就像您从现实世界中的用例中得出的结论一样。
行为 printf
通过 int
范围之外 short
应该将其印刷为 short
, ,但是到目前为止,最简单的实现只是将原始的降低丢弃,尽管规格没有 要求 任何特定的行为,几乎任何理智的实现都将仅执行截断。不过,通常有更好的方法来做到这一点。
如果printf没有填充值或显示签名值的无符号表示形式, %h
不是很有用。
我唯一能想到的用途是通过 unsigned short
或者 unsigned char
并使用 %x
转换说明符。你不能简单地使用裸露 %x
- 价值可以晋升为 int
而不是 unsigned int
, ,然后您的行为不确定。
您的替代方案要么明确提出论据 unsigned
;或使用 %hx
/ %hhx
有一个裸露的争论。
variadic论点 printf()
Et al会使用默认转换自动促进 short
或者 char
值晋升为 int
传递到功能时。
在没有 h
或者 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位数量;当然,该标准实际上并不能保证这一点。
我发现在格式化未签名的字符送入十六进制时避免铸造很有用:
sprintf_s(tmpBuf, 3, "%2.2hhx", *(CEKey + i));
这是一个较小的编码便利性,看起来比多个演员(IMO)更干净。
它方便的另一个位置是SNPRINTF尺寸检查。 GCC7使用SNPRINTF时添加了尺寸检查,以便这将失败
char arr[4];
char x='r';
snprintf(arr,sizeof(arr),"%d",r);
因此,它迫使您在格式化char时使用%d时使用更大的字符
这是一个提交的提交,显示这些修复程序而不是增加Char数组的大小,而将其更改为%H。这也提供了更准确的描述
我同意您的观点,这不是严格必要的,因此仅出于这个原因,在C库函数中并不是一件好事:)
对于不同旗帜的对称性,它可能是“不错的” int
“ 规则。