为什么printf的hh和h长度修饰符存在?

Why does printf's hh and h length modifiers exist?

在可变参数函数中,会发生默认参数提升。

6.5.2.2.6 If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions. [...]

6.5.2.2.7 [...] The ellipsis notation in a function prototype declarator causes argument type conversion to stop after the last declared parameter. The default argument promotions are performed on trailing arguments.

因此,

signed char c = 123;
int         i = 123;
float       f = 123;
double      d = 123;

printf("%d\n", i);   // ok
printf("%d\n", c);   // ok, even though %d expects int.
printf("%f\n", d);   // ok
printf("%f\n", f);   // ok, even though %f expects double.

那么为什么 char (hh) 和 short (h) 有一个 printf 长度修饰符?


章节编号参考N2176

考虑这个例子:

#include <stdio.h>
int main(void)
{
    unsigned short x = 32770;
    printf("%d\n", x) ;  // (1)
    printf("%u\n", x) ;  // (2)
}

在典型的 16 位实现中,默认参数提升采用 unsigned shortunsigned int,而在典型的 32 位实现中,unsigned short 变为 int .

所以在16位系统上(1)是UB,(2)是正确的,但是在32位系统上,(1)是正确的,(2)是正确还是UB,还有待商榷。

使用 %hu 打印 x 适用于所有系统,您不必考虑这些问题。

可以在具有 sizeof(int) == 1 的系统上为 char 构造一个类似的示例。

这是为了向后兼容。

在 C89 标准的草案版本中,printing a signed int, short or char with the %xformat specifier is not undefined behavior:

d, i, o, u, x, X The int argument is converted to signed decimal ( d or i ), unsigned octal ( o ), unsigned decimal ( u ), or unsigned hexadecimal notation ( x or X ); the letters abcdef are used for x conversion and the letters ABCDEF for X conversion. The precision specifies the minimum number of digits to appear; if the value being converted can be represented in fewer digits, it will be expanded with leading zeros. The default precision is 1. The result of converting a zero value with an explicit precision of zero is no characters.

这似乎记录了预先标准化的 C,使用 %x 等格式说明符作为 有符号 值是一种现有做法,因此使用 hhh 长度修饰符可能存在。

如果没有 hhh 长度修饰符,具有位模式 0xFFsigned char 值将打印在 32 位-int如果使用简单的 %X 格式说明符打印,则系统为 0xFFFFFFFF

具体关于 hh 说明符,它在 C99 中明确添加,以便利用来自 stdint.h/inttypes.h 的所有默认固定大小类型的打印。 C99 强制要求类型 int_leastn_t 从 8 到 64,因此需要相应的格式说明符。

来自 C99 原理 5.10,§7.19.6.1 (fprintf):

The %hh and %ll length modifiers were added in C99 (see §7.19.6.2).

§7.19.6.2 (fscanf):

A new feature of C99: The hh and ll length modifiers were added in C99. ll supports the new long long int type. hh adds the ability to treat character types the same as all other integer types; this can be useful in implementing macros such as SCNd8 in <inttypes.h> (see 7.18).

在 C99 之前,只有 dhl 用于打印整数类型。例如,在 C99 中,常规实现可以将 inttypes.h 说明符定义为:

#define SCNi8   hh
#define SCNi16   h
#define SCNi32   d
#define SCNi64  ll

现在默认参数 promotions 成为 printf/scanf 实现的头疼,而不是 inttypes.h 实现。

它们不是为了 printf() 用法,而是为了 scanf() 能够使用对 short 整数和 char 整数的引用。为了统一性和完整性,printf() 函数接受它们,但它们是不可区分的,因为 printf() 的 vaarg 参数对于类型为 [=12= 的所有参数提升为 int ] 和 char 整数值。所以它们在 printf() 中是等价的,但在 scanf() 和 friends 中不等价。