为什么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 short
到 unsigned 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 %x
format 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
等格式说明符作为 有符号 值是一种现有做法,因此使用 h
和 hh
长度修饰符可能存在。
如果没有 h
和 hh
长度修饰符,具有位模式 0xFF
的 signed 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 之前,只有 d
、h
和 l
用于打印整数类型。例如,在 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 中不等价。
在可变参数函数中,会发生默认参数提升。
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 todouble
. 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 short
到 unsigned 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 %x
format 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
等格式说明符作为 有符号 值是一种现有做法,因此使用 h
和 hh
长度修饰符可能存在。
如果没有 h
和 hh
长度修饰符,具有位模式 0xFF
的 signed 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
andll
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 asSCNd8
in<inttypes.h>
(see 7.18).
在 C99 之前,只有 d
、h
和 l
用于打印整数类型。例如,在 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 中不等价。