无符号字符的格式说明符

17

假设我想打印 unsigned char

unsigned char x = 12;

哪个是正确的。 这个是:

printf("%d",x);

或者这个:
printf("%u",x);

我在其他地方看到了这样的讨论:

- 即使将 ch 改为 unsigned char,代码的行为也不符合 C 标准的规定。这是因为在普通的 C 实现中,unsigned char 被提升为 int,因此一个 int 会被传递给 printf 的 %u 指示符。但是,%u 需要一个 unsigned int,所以类型不匹配,C 标准没有定义这种行为。

- 你的评论是不正确的。C11 标准规定转换指示符必须与函数参数本身的类型相同,而不是推广类型。这一点在 hh 长度修改符的描述中也有明确说明:“根据整数提升,参数已被提升,但其值在打印之前必须转换为 signed char 或 unsigned char”。

那么哪个是正确的?有什么可靠的来源关于这个问题吗?(从这个意义上讲,我们也应该使用 %d 打印 unsigned short int,因为它可以升级为 int?)

4个回答

11

正确的是*:

printf("%d",x);

这是由于`printf()`是可变参数函数,因此会出现默认参数提升。这意味着`unsigned char`值始终会被提升为`int`。
根据N1570(C11草案)的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.”
这些整数提升在6.3.1.1/2节“布尔值、字符和整数”中进行了定义。
如果一个能够表示原始类型的所有值(对于位字段来说,受宽度限制),则该值被转换为;否则,它将被转换为。这些称为整数提升.58)。所有其他类型都不受整数提升的影响。 这段引用回答了您关于的第二个问题(请参阅下面的评论)。

* 除了超过8位的unsigned char(例如,它可能占用16位),请参见@chux的answer


10

对于unsigned char x = 12的正确格式说明符取决于多种因素:

如果INT_MAX >= UCHAR_MAX,这通常是情况,使用"%d"。在这种情况下,unsigned char被提升为int

printf("%d",x);

否则使用"%u"(或"%x""%o")。在这种情况下,unsigned char被提升为unsigned
printf("%u",x);

现代编译器支持"hh"长度修饰符,以解决这种歧义。如果由于可变参数的标准提升而将x转换为intunsigned,则printf()会在打印之前将其转换为unsigned char

printf("%hhu",x);

如果使用旧编译器,没有使用"hh"或需要高度可移植的代码,请使用显式转换。

printf("%u", (unsigned) x);

同样的问题/答案适用于"unsigned short",只是期望"INT_MAX >= USHRT_MAX"并使用"h"代替"hh"。

1
我刚刚得出了同样的结论。在使用无符号字符时,使用d可能会导致未定义行为,请参见讨论(请查看我的最新评论):https://dev59.com/5ZXfa4cB1Zd3GeqPh6IZ#36350763。我想听听你的意见。 - 2501
1
@2501 当打印 charunsigned charsigned char...intunsigned 时,请考虑什么是 defined 并从2个规范开始:(1)任何低于int/unsigned的较低级整数将被提升为int/unsigned。 (2) ...参数:“...一个提升类型是有符号整数类型,另一个提升类型是相应的无符号整数类型,并且该值在两种类型中都可以表示”(C11 §6.5.2.2)。因此,在考虑是否使用printf()定义"hhd"、"hhu"、..."d"、"u"时,原始类型不相关,只关注提升类型和值。 - chux - Reinstate Monica
1
@2501... 那么我们只需要考虑以下情况,以确定行为是否被定义:A) 使用具有“d”、“hd”、“hhd”限定符的int参数(或在int范围内的unsigned参数),其值是在intshortsigned char范围内;B)使用具有unsigned参数(或在unsigned范围内的int参数)且值为任何值的“...u”、“...o”、“...x”、“...X”。我认为像A)中值超出范围的情况取决于实现。 - chux - Reinstate Monica
你能在“寻找高度可移植代码”方面添加一些信息吗? - undefined

2

对于跨平台开发,我通常通过使用inttypes.h来避免推广问题。

http://pubs.opengroup.org/onlinepubs/009695399/basedefs/inttypes.h.html

这个头文件(在C99标准中)定义了所有基本类型的printf类型。所以如果你想要一个uint8_t(我强烈建议使用这种语法,而不是unsigned char),我会使用

#include <inttypes.h>
#include <stdint.h>
uint8_t x;
printf("%" PRIu8 "\n",x);

3
对于跨平台开发,unsigned char 的范围可能与 uint8_t 的范围不匹配。符合 C11 标准的编译器不一定需要实现 uint8_t - "这些类型是可选的"。 - chux - Reinstate Monica
2
@chux 当平台没有8位可寻址单元时,如果希望引起编译错误而不是错误行为,则应使用uint8_t - M.M

2

unsigned charunsigned short都可以安全地使用%u进行打印。默认参数提升将它们转换为intunsigned int之一。如果它们被提升为后者,一切都很好(格式说明符和传递的类型匹配),否则C11(n1570)6.5.2.2 p6,第一个弹头适用:

  • 一个提升的类型是带符号整数类型,另一个提升的类型是相应的无符号整数类型,并且值在两种类型中都可以表示;

标准非常清楚地指出默认参数提升适用于printf的可变参数,例如再次提到了(大多数无用的)hhh长度修饰符(ibid。7.21.6.1 p7,emph. mine):

hh -- 指定以下diouxX转换说明符应用于signed charunsigned char参数(参数将根据整数提升进行提升,但在打印之前其值应转换为signed charunsigned char);[...]


1
是的,hhh对于printf()来说大多无用,但它们主要是为了对称性而添加的,因为它们对于scanf()并不无用。 - RastaJedi
我认为这解释了。我曾经对此感到疑惑,然后发现了这个。 - natersoz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接