printf的h和hh修饰符的用途是什么?

时间:2011-01-03 18:02:23

标签: c printf variadic-functions promotions format-specifiers

除了%hn%hhn(其中hhh指定指向对象的大小), h格式说明符的hhprintf修饰符的重点是什么?

由于标准要求对可变函数应用的默认促销,不可能将charshort类型的参数(或其任何有符号/无符号变体)传递给{ {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参数。

如果参数实际上是hshort类型,那么升级到unsigned short,然后转换回intshort将产生相同的作为促销unsigned short而不进行任何转换。因此,对于intshort类型的参数,unsigned short%d等应该为%u%hd等提供相同的结果。(同样适用于%hu类型和char)。

据我所知,hhh修饰符可能有用的唯一情况是参数在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,从而改变了打印的值。但标准是否指定了这种行为,还是一个破坏软件可能依赖的实现细节?)

7 个答案:

答案 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 shortunsigned char并使用%x转化说明符。您不能简单地使用裸%x - 该值可能会提升为int而不是unsigned int,然后您就会有未定义的行为。

你的选择要么是明确地将论点强加给unsigned;或者使用%hx / %hhx和一个简单的论点。

答案 3 :(得分:1)

使用默认转换自动提升printf()等的可变参数,因此当传递给函数时,任何shortchar值都会提升为int

如果没有hhh修饰符,则必须屏蔽传递的值以可靠地获取正确的行为。使用修改器,您不再需要屏蔽值; printf()实现正确完成了工作。

具体来说,对于格式%hxprintf()中的代码可以执行以下操作:

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的字符数组大小。这也给出了更准确的描述

https://github.com/Mellanox/libvma/commit/b5cb1e34a04b40427d195b14763e462a0a705d23#diff-6258d0a11a435aa372068037fe161d24