问题 unsigned char的格式说明符


说我想要打印 unsigned char

unsigned char x = 12;

哪个是对的。这个:

printf("%d",x);

或这个:

printf("%u",x);

事情就在其他地方我遇到过这样的讨论:

- 即使ch已更改为unsigned char,代码的行为也不是由C标准定义的。这是因为unsigned char被提升为int(在普通的C实现中),因此将int传递给printf以获取指定符%u。但是,%u期望unsigned int,因此类型不匹配,并且C标准不定义行为

- 你的评论不正确。 C11标准规定转换说明符必须与函数参数本身的类型相同,而不是提升类型。这一点也在hh length修饰符的描述中得到了具体解决:“参数将根据整数提升进行提升,但其值应在打印前转换为signed char或unsigned char”

哪个是正确的?任何可靠的消息来源都说这个问题? (在这个意义上我们也应该打印 unsigned short int与%d因为它可以被提升为 int?)。


4408
2017-12-18 13:10


起源



答案:


正确的是*:

printf("%d",x);

这是因为 默认参数促销 如 printf() 是可变函数。这意味着 unsigned char 值始终提升为 int

从N1570(C11草案) 6.5.2.2/6  函数调用 (强调我的前进):

如果表示被调用函数的表达式具有该类型   不包括原型, 整数促销 执行   每个参数和具有类型的参数 float 晋升为    double。这些被称为 默认参数促销

6.5.2.2/7 子条款告诉:

函数原型声明符中的省略号表示法导致   参数类型转换在最后声明的参数后停止。   该 对尾随参数执行默认参数提升

这些整数促销定义在 6.3.1.1/2  布尔值,字符和整数

如果 int  可以表示原始类型的所有值 (受限制   通过宽度,对于位字段),该值被转换为a int;   否则,它被转换为 unsigned int。这些被称为    整数促销0.58) 所有其他类型的整数不变   促销活动。

这句话回答了你的第二个问题 unsigned short (见下面的评论)。


*超过8位除外 unsigned char (例如,它可能占据16位),请参阅@ chux's 回答


7
2017-12-18 13:12





正确的格式说明符 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 升职 int要么 unsigned 由于可变参数的标准促销, printf() 将其转换为 unsigned char 在打印之前。

printf("%hhu",x);

如果没有处理旧的编译器 "hh" 或寻求高度可移植的代码,使用显式转换

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

相同的问题/答案适用于 unsigned short,期待 INT_MAX >= USHRT_MAX 并使用 "h" 代替 "hh"


6
2017-12-18 17:26



我刚刚得出了同样的结论。运用 d 使用unsigned char可能会导致ub,请参阅讨论(请参阅我的最新评论): stackoverflow.com/a/36350763/4082723 我想听听你的意见。 - 2501
@ 2501打印时 char, unsigned char, signed char ... int, unsigned,考虑一下 定义  并从2个规范开始:(1)任何低于整数的整数 int/unsigned 将升职 int/unsigned。 2) ... 参数:“...一个提升类型是有符号整数类型,另一个提升类型是相应的无符号整数类型,并且该值可在两种类型中表示”(C11§6.5.2.2)。所以在考虑是否 ”hhd”, “hhu”, … “d”, “u”是的 定义 同 printf(), 原始类型无关紧要,只有提升类型和价值是值得关注的。 - chux
@ 2501 ...然后我们只需要考虑这些情况,看看是否有行为 定义:A)说明“d”,“hd”,“hhd”带有 int 论证(或 unsigned 范围内的论证 int)并且值在范围内 int, short, signed char,B)“...... u”,“...... o”,“...... x”,“...... X”带有 unsigned 论证(或 int 范围内的论证 unsigned),任何价值。一世 认为 像A)这样的情况,其值超出范围是依赖于实现的。 - chux


答案:


正确的是*:

printf("%d",x);

这是因为 默认参数促销 如 printf() 是可变函数。这意味着 unsigned char 值始终提升为 int

从N1570(C11草案) 6.5.2.2/6  函数调用 (强调我的前进):

如果表示被调用函数的表达式具有该类型   不包括原型, 整数促销 执行   每个参数和具有类型的参数 float 晋升为    double。这些被称为 默认参数促销

6.5.2.2/7 子条款告诉:

函数原型声明符中的省略号表示法导致   参数类型转换在最后声明的参数后停止。   该 对尾随参数执行默认参数提升

这些整数促销定义在 6.3.1.1/2  布尔值,字符和整数

如果 int  可以表示原始类型的所有值 (受限制   通过宽度,对于位字段),该值被转换为a int;   否则,它被转换为 unsigned int。这些被称为    整数促销0.58) 所有其他类型的整数不变   促销活动。

这句话回答了你的第二个问题 unsigned short (见下面的评论)。


*超过8位除外 unsigned char (例如,它可能占据16位),请参阅@ chux's 回答


7
2017-12-18 13:12





正确的格式说明符 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 升职 int要么 unsigned 由于可变参数的标准促销, printf() 将其转换为 unsigned char 在打印之前。

printf("%hhu",x);

如果没有处理旧的编译器 "hh" 或寻求高度可移植的代码,使用显式转换

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

相同的问题/答案适用于 unsigned short,期待 INT_MAX >= USHRT_MAX 并使用 "h" 代替 "hh"


6
2017-12-18 17:26



我刚刚得出了同样的结论。运用 d 使用unsigned char可能会导致ub,请参阅讨论(请参阅我的最新评论): stackoverflow.com/a/36350763/4082723 我想听听你的意见。 - 2501
@ 2501打印时 char, unsigned char, signed char ... int, unsigned,考虑一下 定义  并从2个规范开始:(1)任何低于整数的整数 int/unsigned 将升职 int/unsigned。 2) ... 参数:“...一个提升类型是有符号整数类型,另一个提升类型是相应的无符号整数类型,并且该值可在两种类型中表示”(C11§6.5.2.2)。所以在考虑是否 ”hhd”, “hhu”, … “d”, “u”是的 定义 同 printf(), 原始类型无关紧要,只有提升类型和价值是值得关注的。 - chux
@ 2501 ...然后我们只需要考虑这些情况,看看是否有行为 定义:A)说明“d”,“hd”,“hhd”带有 int 论证(或 unsigned 范围内的论证 int)并且值在范围内 int, short, signed char,B)“...... u”,“...... o”,“...... x”,“...... X”带有 unsigned 论证(或 int 范围内的论证 unsigned),任何价值。一世 认为 像A)这样的情况,其值超出范围是依赖于实现的。 - chux


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

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

标准非常明确,默认参数提升适用于可变参数 printf,例如它再次被提到(大多数没用) h 和 hh 长度修饰符(同上7.21.6.1 p7,emph.mine):

hh  - 指定以下内容 dioux, 要么 X 转换说明符适用于a signed char要么 unsigned char 论证(该参数将根据整数提升进行推广,但其价值应转换为 signed char要么 unsigned char 打印前); [...]


2
2017-12-18 20:10



是 h 和 hh 是 大多没用 对于 printf(),但它们大多是为了对称而添加的,因为它们并非无用 scanf()。 - RastaJedi
我认为这解释了它。我很想知道这件事并且碰到了这个。 - natersoz


对于跨平台开发,我通常会通过使用来绕过促销问题 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);

1
2017-12-18 13:51



对于跨平台开发,范围 unsigned char 可能与范围不匹配 uint8_t。兼容的C11编译器不必实现 uint8_t  - “这些类型是可选的。” - - chux
@chux uint8_t 当平台没有8位可寻址单元时,当需要引起编译错误而不是错误行为时,将使用它。 - M.M