除了%hn
和%hhn
(其中h
或hh
指定指向对象的大小), h
格式说明符的hh
和printf
修饰符的重点是什么?
由于标准要求对可变函数应用的默认促销,不可能将char
或short
类型的参数(或其任何有符号/无符号变体)传递给{ {1}}。
根据7.19.6.1(7),printf
修饰符:
指定以下d,i,o,u,x或X转换指定适用于 short int或unsigned short int参数(参数将为 已根据整数促销推广,但其价值应为 在打印之前转换为short int或unsigned short int;); 或者以下n个转换指定符适用于指向short的指针 int参数。
如果参数实际上是h
或short
类型,那么升级到unsigned short
,然后转换回int
或short
将产生相同的值作为促销unsigned short
而不进行任何转换。因此,对于int
或short
类型的参数,unsigned short
,%d
等应该为%u
,%hd
等提供相同的结果。(同样适用于%hu
类型和char
)。
据我所知,hh
或h
修饰符可能有用的唯一情况是参数在hh
范围之外传递int
}或short
,例如
unsigned short
但我的理解是,传递这样的错误类型会导致不确定的行为,所以你不能指望它打印0。
我见过的一个真实案例是这样的代码:
printf("%hu", 0x10000);
作者希望它打印char c = 0xf0;
printf("%hhx", c);
,尽管实施时已经签署了f0
类型的明文{在这种情况下,char
会打印printf("%x", c)
或类似内容。但这种期望值得保证吗?
(注意:原因是原始类型为fffffff0
,会被提升为char
并转换回int
而不是unsigned char
,从而改变了打印的值。但标准是否指定了这种行为,还是一个破坏软件可能依赖的实现细节?)
答案 0 :(得分:13)
一个可能的原因:在格式化的输入函数中使用那些修饰符的对称性?我知道这不是绝对必要的,但是可能有价值吗?
尽管他们没有在the C99 Rationale document中提及对称性对于“h”和“hh”修饰符的重要性,但委员会确实提到它是为什么支持“%p”转换说明符的原因。 fscanf()
(即使这不是C99的新内容 - “%p”支持在C90中):
将带有%p的输入指针转换添加到C89,尽管使用fprintf对称性显然存在风险。
在fprintf()
的部分中,C99基本原理文件确实讨论了添加“hh”,但仅仅是将读者引用到fscanf()
部分:
在C99中添加了%hh和%ll长度修饰符(参见§7.19.6.2)。
我知道这是一个微妙的线索,但无论如何我都在猜测,所以我想我会给出任何可能的论据。
另外,为了完整性,“h”修饰符符合最初的C89标准 - 可能即使由于广泛的现有用途而不是严格必要,即使可能没有技术要求,它也会存在。使用修饰符。
答案 1 :(得分:5)
在%...x
模式下,所有值都被解释为无符号。因此,负数会被打印为未经签名的转换。在大多数处理器使用的2的补码算法中,有符号的负数和它的正无符号等价之间的位模式没有区别,后者由模运算定义(将字段的最大值加1加到负数,根据符合C99标准)。许多软件 - 尤其是最有可能使用%x
的调试代码 - 做出了一个默认的假设,即有符号的负值及其无符号转换的位表示是相同的,这只适用于2的补码机器。 / p>
这个演员的机制使得值的十六进制表示总是暗示,可能是不准确的,数字已经以2的补码呈现,只要它没有达到不同整数表示具有不同的边缘条件范围。这甚至适用于算术表示,其中值0未用全0的二进制模式表示。
由于促销中隐含的符号扩展,因此在任何计算机上,在{16}中显示为short
的否定unsigned long
将被f
填充,{{1}将打印。 值是相同的,但它确实在视觉上误导了字段的大小,这意味着大量的范围根本不存在。
printf
截断显示的表示以避免此填充,正如您从实际用例中得出的结论。
%hx
的行为在printf
范围之外传递int
时未定义,应该打印为short
,但迄今为止最简单的实施只是丢弃原始向下转换的高位,所以虽然规范不要求任何特定的行为,但几乎任何理智的实现都只是执行截断。不过,通常有更好的方法。
如果printf没有填充值或显示有符号值的无符号表示,short
不是很有用。
答案 2 :(得分:5)
我能想到的唯一用途是传递unsigned short
或unsigned char
并使用%x
转化说明符。您不能简单地使用裸%x
- 该值可能会提升为int
而不是unsigned int
,然后您就会有未定义的行为。
你的选择要么是明确地将论点强加给unsigned
;或者使用%hx
/ %hhx
和一个简单的论点。
答案 3 :(得分:1)
使用默认转换自动提升printf()
等的可变参数,因此当传递给函数时,任何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位数量;当然,标准并不能保证这一点。
答案 4 :(得分:1)
我发现在将无符号字符格式化为十六进制时避免强制转换很有用:
sprintf_s(tmpBuf, 3, "%2.2hhx", *(CEKey + i));
这是一个小编码方便,看起来比多个演员(IMO)更清晰。
答案 5 :(得分:0)
我同意你的观点,这并不是绝对必要的,所以单凭这个理由对C库函数来说并不好:)
对于不同标志的对称性可能“很好”,但它主要是适得其反,因为它隐藏了“转换为int
”规则。
答案 6 :(得分:0)
另一个方便的是snprintf尺寸检查。 gcc7在使用snprintf时添加了大小检查 所以这将失败
char arr[4];
char x='r';
snprintf(arr,sizeof(arr),"%d",r);
因此在格式化char
时使用%d时会强制使用更大的char这是一个提交,显示这些修复,而不是增加他们将%d更改为%h的字符数组大小。这也给出了更准确的描述