68

除了%hnand %hhn(其中horhh指定了指向对象的大小),格式说明符的handhh修饰符的意义是什么?printf

由于标准要求将默认提升应用于可变参数函数,因此不可能将类型charshort(或其任何有符号/无符号变体)的参数传递给printf.

根据 7.19.6.1(7),h修改器:

指定后面的 d、i、o、u、x 或 X 转换说明符适用于 short int 或 unsigned short int 参数(该参数将根据整数提升进行提升,但其值应转换为 short int或 unsigned short int 打印前);或者后面的 n 转换说明符适用于指向短 int 参数的指针。

如果参数实际上是shortor类型unsigned short,则提升 toint后跟转换回shortorunsigned short将产生与提升 to相同的int,而没有任何转换回。因此,对于shortor类型的参数unsigned short%d,%u等应该给出与 , 等相同的结果%hd%hu对于char类型 and也是如此hh)。

据我所知,horhh修饰符可能有用的唯一情况是当参数传递给它时int超出shortor的范围unsigned short,例如

printf("%hu", 0x10000);

但我的理解是,像这样传递错误的类型无论如何都会导致未定义的行为,所以你不能指望它打印 0。

我见过的一个真实案例是这样的代码:

char c = 0xf0;
printf("%hhx", c);

作者希望它在哪里打印f0尽管实现具有已char签名的普通类型(在这种情况下,printf("%x", c)将打印fffffff0或类似)但这种期望是否合理?

(注意:发生的事情是原始类型是char,它被提升int并转换回unsigned char而不是char,从而更改了打印的值。但是标准是否指定了这种行为,或者它是损坏的软件可能的实现细节依赖?)

4

7 回答 7

19

一个可能的原因:在格式化输入函数中使用这些修饰符是为了对称?我知道这不是绝对必要的,但也许看到了它的价值?

尽管他们在C99 基本原理文档中没有提到“h”和“hh”修饰符的对称性的重要性,但委员会确实提到了它作为为什么支持“%p”转换说明符的考虑因素fscanf()(即使对于 C99 来说并不新鲜——“%p”支持在 C90 中):

使用 %p 的输入指针转换被添加到 C89 中,尽管这显然是有风险的,因为它与 fprintf 对称。

在关于 的部分中fprintf(),C99 基本原理文档确实讨论了添加了“hh”,但仅将读者引至该fscanf()部分:

在 C99 中添加了 %hh 和 %ll 长度修饰符(参见 §7.19.6.2)。

我知道这是一个脆弱的线程,但我还是在猜测,所以我想我会给出任何可能的论点。

此外,为了完整起见,“h”修饰符在最初的 C89 标准中 - 即使由于广泛存在的使用而并非绝对必要,即使可能没有使用修饰符的技术要求,它也可能存在.

于 2011-01-03T18:21:22.880 回答
5

%...x模式下,所有值都被解释为无符号。因此,负数被打印为它们的无符号转换。在大多数处理器使用的 2 的补码算术中,有符号负数与其正无符号等价物之间的位模式没有区别,后者由模算术定义(将字段的最大值加一到负数,根据符合 C99 标准)。许多软件——尤其是最有可能使用的调试代码%x——都假设有符号负值的位表示和它的无符号强制转换是相同的,这仅在 2 的补码机器上是正确的。

这种转换的机制使得值的十六进制表示总是暗示(可能不准确)一个数字已经以 2 的补码呈现,只要它没有达到不同整数表示具有不同范围的边缘条件。这甚至适用于值 0 不是用全 0 的二进制模式表示的算术表示。

short因此,在任何机器上,显示为十六进制的负数unsigned long都将用 填充f,因为促销中的隐含符号扩展printf将打印出来。该是相同的,但对于字段的大小,它确实在视觉上具有误导性,这意味着根本不存在大量范围。

%hx截断显示的表示以避免这种填充,就像您从实际用例中得出的结论一样。

printf当传递int超出范围时的行为是未定义的short,应该打印为 a short,但迄今为止最简单的实现只是通过原始向下转换丢弃高位,因此虽然规范不需要任何特定行为,但几乎任何理智的实现将只执行截断。不过,通常有更好的方法来做到这一点。

如果 printf 不是填充值或显示有符号值的无符号表示,%h则不是很有用。

于 2011-01-03T18:22:16.207 回答
5

我能想到的唯一用途是传递unsigned shortunsigned char并使用%x转换说明符。您不能简单地使用裸%x- 值可能会提升为int而不是unsigned int,然后您就会有未定义的行为。

您的替代方法是将参数显式转换为unsigned; 或将%hx/%hhx与裸参数一起使用。

于 2011-01-04T00:17:59.727 回答
1

et al的可变参数printf()使用默认转换自动提升,因此任何short或值在传递给函数时都会char被提升。int

在没有horhh修饰符的情况下,您必须屏蔽传递的值才能可靠地获得正确的行为。使用修饰符,您不再需要屏蔽值;实施printf()正确地完成了工作。

具体来说,对于 format %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 位的数量。当然,该标准实际上并不能保证这一点。

于 2011-01-03T20:27:39.810 回答
1

另一个方便的地方是 snprintf 大小检查。gcc7 在使用 snprintf 时添加了大小检查,因此这将失败

char arr[4];
char x='r';
snprintf(arr,sizeof(arr),"%d",r);

所以它强制你在格式化字符时使用 %d 时使用更大的字符

这是一个提交,显示了这些修复,而不是增加他们将 %d 更改为 %h 的 char 数组大小。这也给出了更准确的描述

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

于 2018-01-01T09:57:59.477 回答
1

我发现在将无符号字符格式化为十六进制时避免强制转换很有用:

        sprintf_s(tmpBuf, 3, "%2.2hhx", *(CEKey + i));

这是一个小的编码方便,看起来比多次转换(IMO)更干净。

于 2017-12-27T17:49:10.287 回答
0

我同意你的观点,这不是绝对必要的,因此仅凭这个原因在 C 库函数中是没有好处的:)

不同标志的对称性可能“很好”,但它大多适得其反,因为它隐藏了“转换为int”规则。

于 2011-01-03T23:13:06.027 回答