为啥 printf ("%d", ~a);当 a 等于 3 时显示 -4?

Posted

技术标签:

【中文标题】为啥 printf ("%d", ~a);当 a 等于 3 时显示 -4?【英文标题】:Why printf ("%d", ~a); displays -4 when a is equal to 3?为什么 printf ("%d", ~a);当 a 等于 3 时显示 -4? 【发布时间】:2019-04-27 23:59:32 【问题描述】:

运行以下程序显示 -4 而预期为 252:

unsigned char a=3;
printf ("%d", ~a);

为什么这段代码不显示 252?

我还根据建议的答案测试了以下内容:

printf ("%u", ~a);

显示:4294967292

printf ("%hu", ~a);

显示:65532

为什么~a 不返回unsigned char,因为 unsigned char

我的问题不是我应该怎么做才能显示 252 ?我的问题是为什么不显示 252?

【问题讨论】:

您应该使用%u 格式说明符打印,因为您的变量是无符号字符! 这不是“如何在 C 中打印无符号字符?”的副本。请在标记为重复之前阅读问题。 我同意这不是完全重复的,应该重新打开。但是,请不要在您的帖子中使用大写字母,因为这被认为是大喊大叫和不礼貌的。也请不要对您的问题文本进行评论。 我明白,但我有点厌倦了总是与不阅读问题并将其标记为重复的人打架。真的是太有画感了。 您所做的编辑有误。 "%hu" gives you 65532, while "%hhu" should give you the expected 252. 【参考方案1】:

除了the answer of @Someprogrammerdude,以下是The Book的相关段落1)

Unary arithmetic operators (§6.5.3.3/4)

~ 运算符的结果是它的按位补码 (升级 [ !! ] em> ) 操作数(即结果中的每一位都被设置当且仅 如果未设置转换后的操作数中的相应位)。 该 对操作数执行整数提升,结果有 提升的类型。 如果提升的类型是无符号类型,则 表达式 ~E 等价于可表示的最大值 键入减号E

Arithmetic operands - Boolean, characters, and integers (§6.3.1.1):

    每个整数类型都有一个整数转换等级,定义如下:

    任何两个有符号整数类型都不应具有相同的等级,即使它们具有相同的表示形式。 有符号整数类型的等级应大于任何精度较低的有符号整数类型的等级。 long long int的rank要大于long int的rank,long int的rank要大于int的rank,大于short int的秩,大于signed char的秩。 任何无符号整数类型的等级应等于相应的有符号整数类型的等级,如果有的话。 任何标准整数类型的秩都应大于任何具有相同宽度的扩展整数类型的秩。 char的等级应该等于signed charunsigned char的等级。 _Bool 的等级应小于所有其他标准整数类型的等级。 任何枚举类型的等级都应等于兼容整数类型的等级(参见 6.7.2.2)。 任何扩展 signed 整数类型相对于另一个具有相同精度的扩展 signed 整数类型的等级是由实现定义的,但仍受制于其他规则来确定整数转换等级。 对于所有整数类型 T1T2T3,如果 T1 的排名高于 T2T2 的排名高于 T3,然后 T1 的排名高于 T3

    可以在表达式中使用 intunsigned int 的地方:

    具有整数类型的对象或表达式,其整数转换等级小于或等于 intunsigned int 的等级。 _Boolintsigned intunsigned int 类型的位字段。 如果一个int可以表示原始类型的所有值,则将该值转换为一个int;否则,它将被转换为 unsigned int。这些被称为整数提升。48)所有其他类型都不受整数提升的影响。 整数提升保留值,包括符号。如前所述,“普通”char 是否被视为 signed 是实现定义的。

48) 整数提升仅适用于:作为常用算术转换的一部分,适用于某些参数表达式,适用于一元 +-的操作数> 和 ~ 运算符,以及移位运算符的两个操作数,由它们各自的子条款指定。

您的问题:

为什么~a 不返回unsigned char,因为aunsigned char

因为整数促销适用。

unsigned char a = 3;
printf ("%d", ~a);

a 是一个unsigned char,它的范围可以用int 表示。所以a 被提升为int。假设 32 位宽 ints 和 two's complement:

  310 = 0000 0000 0000 0000 0000 0000 0000 00112 ~310 = 1111 1111 1111 1111 1111 1111 1111 11002 解释为 signed int 的结果是否定的,因为设置了最高有效位,即符号位。 转换为十进制:1111 1111 1111 1111 1111 1111 1111 11002 ¬ 0000 0000 0000 0000 0000 0000 0000 00112 + 0000 0000 0000 0000 0000 0000 0000 00012 ────────────────────────────0000 0000 0000 0000 0000 0000 0000 01002

01002 = 0 × 23 + 1 × 22 + 0 × 22 + 0 × 22 1 × 22 =   410 = −410(带有原始符号)

~> printf() 打印 -4

要使用使用"%d" 作为格式说明符的原始代码获得所需的 252 结果,需要进行一些转换:

unsigned char a = 3;
printf("%d\n", (int)((unsigned char) ~a));  // prints 252
//              ^^^   ^^^^^^^^^^^^^
//               |          cast the result of ~a back to unsigned char *)
//               |          to discard the bits > CHAR_BIT
//               cast the char back to int to agree with the format specifier 

*)感谢chux让我记住char可能是signed!转换为(可能是 signedchar 会给出错误的结果 -4。

要在不进行强制转换的情况下获得相同的结果,您可以使用长度修饰符hh

The fprintf function (§7.19.6.1/7)

长度修饰符及其含义是:hh  指定后面的di ouxX 转换说明符适用于 signed charunsigned char 参数(该参数将根据整数提升进行提升,但其值应在打印前转换为 signed charunsigned char) ;或者后面的 n 转换说明符适用于指向有符号字符参数的指针。[...]

unsigned char a = 3;
printf("%hhu\n", ~a);  // prints 252

你其他尝试的问题:

printf ("%u", ~a);

显示:4294967292

printf ("%hu", ~a);

显示:65532

由于~ainteger,它不是格式说明符u 的正确类型,并且

The fprintf function (§7.19.6.1/9):

如果转换规范无效,则行为未定义。248)如果任何参数不是相应转换规范的正确类型,则行为未定义。

1) ISO/IEC 9899/Cor3:2007 aka C99:TR3 aka C99

【讨论】:

详细信息:~a 作为 int"%u" 是 UB,当 ~a 不在 unsigned 范围内时 - 在这种情况下 (-4)。 C11 §6.5.2.2 6 @chux 如果您引用您所指的段落会更容易。 printf("%d\n", (int)((char) ~a));char 未签名 时打印 252,当char 已签名 时打印 -4。需要审查。建议printf("%u\n", (unsigned char) ~a); "~a 是一个整数,它不是格式说明符 u 的正确类型"导致 UB 是不够的。 “~a 是一个整数,它不是格式说明符 u 的正确类型”并且 ~a not in unsigned range 是 UB。 这不是 promotion 问题,而是“值在两种类型中都可以表示”的问题。 printf("%u\n", 42); printf("%d\n", 42u); 不是 UB。 printf("%u\n", -42); 是 UB。【参考方案2】:

因为~aint 而不是字符。如果要将其打印为 8 位无符号整数类型,请使用格式 "%hhu",如

printf("%hhu\n", ~a);

hh 前缀是 8 位部分,u 是无符号部分。

了解integer promotions 以及two's complement 如何表示负数也可能会有所帮助。

来自关于整数承诺的链接:

整数提升被应用...到一元按位运算符~的操作数

您的问题是上面讨论的各种问题。

【讨论】:

@Fifi 因为在所有常见平台上,int 是 32 位,恰好是 4 个字节,这是 sizeof 运算符正确报告的。 @Fifi 因为sizeof 在您的系统上为int 提供4(字节)。请注意sizeof 产生一个size_t 类型的值,您应该使用"%zu"*printf() 它。将~ 应用于您使用sizeofint 是无关紧要的。 @Fifi 哦,说真的,要打印sizeof 的结果,您应该使用z 前缀,如"%zu"(因为size_t,这是sizeof 的结果是无符号的)。参见例如this printf (and family) reference. @Fifi 因为所有较小的类型 int 在对其执行操作时都会得到提升:Understanding interger conversation rules @Fifi 请阅读我提供的链接。关于integer promotion 的内容包括文本“整数提升应用于...到一元按位运算符~ 的操作数”【参考方案3】:

为什么是 printf ("%d", ~a);当 a 等于 3 时显示 -4? 为什么~a 不返回unsigned char,因为aunsigned char

unsigned char a=3;

因为~aint -4,而不是unsigned char 252,这是由于整数促销


~ 的应用导致a整数提升采用按位补码之前。

如果int可以表示原始类型的所有值...,则该值将转换为int;否则,它将转换为unsigned int。这些被称为整数提升

~3,给定常见的 2 的补码整数编码,为 -4。


在某些平台上,unsigned char, int, unsigned 的位宽相同。在那些特殊的平台上,~a 可能会采用类似FFFFFFFC (4294967292) 的值,因为它会被提升为unsigned

【讨论】:

以上是关于为啥 printf ("%d", ~a);当 a 等于 3 时显示 -4?的主要内容,如果未能解决你的问题,请参考以下文章

c语言中a=2;printf("a=%%d",a);为啥答案是a=%d?

【求解】为啥会陷入死循环?

形参不能改变实参,为啥在用指针传递数值的时候又可以改变呢?

c语言int k=10; for(;!k;k--)printf("%d",k--)为啥无答案

c语言请问while循环为啥执行不了?

为啥出现输入数字后出现log10: SING error