如何在C语言中打印一个无符号字符?

73

我想将字符打印为正值:

char ch = 212;
printf("%u", ch);

但我得到:

4294967252

如何在输出中获取212

6个回答

45

将你的ch声明为

unsigned char ch = 212 ;

而且你的printf会正常工作。


15
即使将代码中的ch改为unsigned char,该代码的行为仍未被C标准定义。这是因为在正常的C实现中,unsigned char会被提升为一个int,因此printf会接收到一个int来匹配%u格式指示符。然而,%u需要一个unsigned int,所以类型不匹配,C标准没有定义该行为。 - Eric Postpischil
5
你的评论是不正确的。C11标准规定转换说明符必须与函数参数本身的类型相同,而不是晋升后的类型。这一点在hh长度修饰符的描述中也有具体说明:“参数将根据整数提升进行晋升,但在打印之前其值应转换为有符号字符或无符号字符。” - andypea
只有在传递给 printf() 时进行了明确的 unsigned int 强制转换才起作用。添加 -std=c11gcc-4.8.4 命令中以强制执行标准没有任何效果。 - ypx
2
@andrew.punnett:函数参数是提升后的值;它具有整数提升所产生的类型,根据C 2011 6.5.2.2 6。在hh的情况下,标准告诉我们,在提升之前的类型可能是signed charunsigned char。这意味着ch仍将被提升为int,但转换%hhu期望如此。然而,在printf(%u,ch)中不存在hh的情况下,这与本情况无关。 - Eric Postpischil
如果我定义 unsigned char a = -2 和 signed char a = -1,底层的位模式会相同吗?它会是什么? - Suraj Jain
显示剩余2条评论

41

这是因为在此情况下,您的系统上* char 类型被视为有符号类型。当发生这种情况时,在将数据传递给具有可变参数的函数时进行默认转换时,数据会进行符号扩展。由于212大于0x80,它被视为负数,%u 将该数字解释为一个大正数:

212 = 0xD4

当进行符号扩展时,FF会被添加到您的数字前面,因此它变成:

0xFFFFFFD4 = 4294967252

这是要打印的数字。

请注意,此行为特定于您的实现。根据C99规范,所有char类型都会被提升为(带符号的)int,因为一个int可以表示char的所有值,有符号或无符号:

6.1.1.2:如果一个int能够表示原始类型的所有值,则该值将转换为int;否则,它将转换为unsigned int

这导致将int传递给格式指示器%u,而该指示器期望的是unsigned int

为避免程序出现未定义行为,请添加显式类型转换,如下所示:

unsigned char ch = (unsigned char)212;
printf("%u", (unsigned int)ch);


* 一般而言,标准将 char 的有无符号性质留给实现去决定,请参考这个问题以了解更多细节。


1
这个答案混乱且不准确。关于符号扩展和对212的处理的句子没有上下文,未能解释它们适用的地方(前面的句子适用于后续操作,即将char作为参数时的整数提升,而后面的句子适用于早期操作,即使用int初始化char)。 - Eric Postpischil
1
212被视为负数是因为它大于0x80的说法是错误的。首先,在大多数C实现中,使用212初始化有符号的char会导致整数溢出,因此行为未定义。在许多C实现中,这会导致将212的二进制补码表示截断为八位,因此将比特解释为负值,因为设置了0x80比特,而不是因为初始值大于0x80(反例包括0x80和0x100)。 - Eric Postpischil
1
char初始化之后,它被用作printf的参数。执行整数提升,导致一个负的int被传递给printf。再次说明,许多C实现使用二进制补码,因此进行符号扩展,正如这个答案所说的那样。此时将值转换为unsigned将产生一个大的正数。然而,这个答案没有解释实际行为是未定义的,因为一个int被传递给了%u的指定符,而%u期望一个unsigned int - Eric Postpischil
@dasblinkenlight “char”不能保证适合于类型“int”,因为“char”可能已被定义为与“unsigned char”相同的行为。只有具有相同符号但更小整数转换等级的整数才保证具有值范围,该值范围是另一种类型值范围的子范围(C99标准的第6.2.5节第8段)。因此,“unsigned char”理论上可以由于其符号和整数转换等级而具有与“unsigned int”一样宽的范围。 - Vilhelm Gray
如果我没记错的话,整数类型的最大值和最小值的大小永远不会超过相应类型的unsigned最大值,因此所有的char值都可以适配于unsigned int - Vilhelm Gray
显示剩余6条评论

22

这段代码中有两个 bug。首先,在大多数带有有符号 char 的 C 实现中,char ch = 212 存在问题,因为 212 无法适应 8 位有符号 char,且 C 标准没有完全定义其行为(它要求实现来定义行为)。正确的写法应该是:

unsigned char ch = 212;

其次,在 printf("%u",ch) 中,正常的 C 实现中,ch 将被提升为 int。然而,%u 指定符期望一个无符号整数类型 unsigned int,当传递错误类型时,C 标准没有定义行为。应改为:

```printf("%hhu", ch)```
printf("%hhu", ch);

(对于 %hhuprintf 期望一个无符号字符,通常在 C 的实现中被提升为 int。)


我建议使用 static_cast 而不是旧式转换。 - Shital Shah
3
@ShitalShah:这个问题标记为C,而不是C++。C没有static_cast运算符。 - Eric Postpischil
啊... 没看到。 - Shital Shah
C语言的哪个版本支持'hh'修饰符? - undefined
@CloudCho:自1999年起,C标准中就已经存在了hh修饰符。 - undefined

2

如果由于某种原因您无法更改声明,则可以执行以下操作:

char ch = 212;
printf("%d", (unsigned char) ch);

0

char的范围是从127到-128。如果你赋值212,ch会存储-44(212-128-128),而不是212。因此,如果您尝试将负数打印为无符号数,则会得到(无符号int的最大值)-abs(number),在这种情况下为4294967252。

因此,如果您想将212存储为它本身,唯一能做的就是将ch声明为

unsigned char ch;

现在ch的范围是0到255。


4
字符范围是127到-128。-- 不是,这个范围是由实现定义的。 - Jim Balter
2
我的意思是我说的话很清楚。一个char甚至不一定是8位,更不用说一定是有符号的了。 - Jim Balter
这个答案显然是错误的。char *的范围确实会变化,它在ARM平台上通常是无符号8位。 - Antti Haapala -- Слава Україні

-1
因为 char 默认是 signed 声明的,这意味着变量的范围是

-127 到 +127>

您的值已经溢出。要获取所需的值,您必须声明 unsigned 修饰符。修饰符 (unsigned) 的范围是:

 0 to 255

要获取任何数据类型的范围,请按照以下步骤进行:2^bit 示例:char长度为8位,只需使用2 ^(power) 8即可获得其范围。


这是不正确的:char默认情况下不是有符号的。标准规定(第6.2.5节,“类型”):“实现必须定义char具有与signed charunsigned char相同的范围、表示和行为”,并没有提到“默认”。char是有符号还是无符号取决于实现的决定。 - phlummox

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