【问题标题】:C: What happens (in detail) in x=~x if x is of type char?C:如果 x 是 char 类型,在 x=~x 中会发生什么(详细)?
【发布时间】:2018-12-10 08:48:53
【问题描述】:

如果我们有以下代码:

char x = -1;
x =~x;

在带有 MS VS 编译器(部分支持 C99)的 x86 平台上 - 运行时具体会发生什么?

据我所知,会发生以下情况(如果我错了,请纠正我):

  • x 被赋值为 -1,由位模式 0xff 表示,因为 char 由一个字节表示。
  • ~ 运算符将 x 提升为 int,也就是说,它在内部使用位模式 0xffffffff。
  • ~ 运算符的结果是 0x00000000(int 类型)。
  • 为了执行分配,整数提升应用(主要)。因为在我们的例子中,右侧的操作数是一个 int,所以不会发生转换。左侧的操作数转换为 int。分配的结果是 0x00000000。
  • 作为副作用,分配的左侧被分配了值 0x00000000。由于 x 是 char 类型,还有另一个隐式转换,将 0x00000000 转换为 0x00。

实际发生的事情太多了——我觉得这有点令人困惑。特别是:我对最后一次隐式转换(从 int 到 char)的理解是否正确?如果作业的结果不能存储在 char 中会怎样?

【问题讨论】:

  • “MS VS 编译器(支持 C99)” - 从什么时候开始?
  • 第四个要点中没有整数提升。 int 值被分配给 char 对象。 int 值将转换为 char。分配中没有左侧的转换。
  • 据我所知,C99 到 C11 之间的行为没有改变。或者从 C90 开始。
  • @maya 右侧被转换为匹配左侧的类型。左侧未转换。

标签: c type-conversion operators


【解决方案1】:

确实~xint 类型。

如果charunsigned,则转换回char 是明确定义的。当然,如果值在char支持的范围内,也是很好定义的。

如果charsigned,则~xchar 的转换是实现定义的,有可能引发实现​​定义的信号。

在您的情况下,您有一个带有 2 的补码 int 和 2 的补码 char 的平台,因此 ~x 被观察为 0。

请注意,MSVC 不完全支持任何 C 标准,也没有声称支持。

【讨论】:

  • 如果值超出范围,则该转换只有实现定义的行为。对于问题中的示例,值是0,它在范围内,因此定义明确。
【解决方案2】:

您几乎是正确的,但错过了 char 具有实现定义的签名。它可以是有符号的也可以是无符号的,这取决于编译器。

在任何一种情况下,8 位 2 的补码字符的位模式确实是 0xFF,而不管其符号性如何。但如果char 已签名,整数提升将保留该符号并且您仍然拥有-1 的值,在32 位计算机上为二进制0xFFFFFFFF。但是如果char 是无符号的,-1 在赋值时会被转换为255,并且整数提升会给出255 (0x000000FF)。所以你会得到不同的结果。

关于~的整数提升,右边只有一个操作符,提升一个。

最后,您将结果分配回char,结果将再次取决于签名。从int 分配到char 时,您将进行隐式“左值转换”。结果是实现定义的 - 您很可能会得到int 的最低有效字节。


从中我们可以了解到:

  • 切勿使用char 存储整数值或进行算术运算。仅用于存储字符。请改用uint8_t
  • 切勿对可能已签名或通过隐式提升以静默方式进行签名的操作数执行按位算术。
  • ~ 运算符特别危险,除非操作数是 unsigned int 或更大的无符号类型。

【讨论】:

  • 如果signed,它也有一个实现定义的补码方案(请注意,如果有符号,C++14 以后坚持使用 2 的补码字符),但 +1 都是一样的。
  • 感谢您暗示 char 的符号性取决于实现。这确实有所作为。
  • 术语“左值转换”不是指从intchar 的转换。 (实际上是指将左值转换为右值,即从对象中检索存储的值)
  • @M.M "赋值表达式的类型是左操作数在左值转换后的类型。"然后必须阅读关于 6.3.2.1 下面的详细说明,其中最相关的部分是“将不具有数组类型的左值转换为存储在指定对象中的值(并且不再是左值);这称为左值转换。”
  • @Lundin 在你的第一个引用中实际上并没有发生左值转换,它只是在指定赋值表达式的类型时提到它。您的第二个引用定义了该术语,这当然不是从 int 值到 char 的转换。它是左值的转换。
【解决方案3】:

据我所知,会发生以下情况(如果我错了,请纠正我):

x 被赋值为 -1,由位模式 0xff 表示,因为 char 由一个字节表示。

1 是一个int 类型的整数常量- 将其否定为 -1 并保持为 int。 -1 分配给 char x 。如果char签名,那么x 的值为-1。如果char无符号,则x 采用CHAR_MAX 的值,这也是UCHAR_MAX。 “位模式 0xff”在这里不相关。

~ 运算符将 x 提升为 int,也就是说,它在内部使用位模式 0xffffffff。

x 被提升为int(或unsigned 在稀有机器上CHAR_MAX == UINT_MAX - 我们将忽略它)。 int 至少为 16 位。 -1 的值,当编码为绝大多数常见的 2 的补码时,是一个全 1 位模式。 (其他可能的编码 - 我们也将忽略它)。如果x 的值为UCHAR_MAX,则x 将具有位模式00...00 1111 1111 - 假设为8 位char。其他可能的宽度 - 我们将忽略的另一件事。

~ 运算符的结果是 0x00000000(int 类型)。

是的,(除非CHAR_MAX == UINT_MAX,在这种情况下它是unsigned 并且值为11...11 0000 0000)。

为了执行分配,整数提升应用(主要)。因为在我们的例子中,右侧的操作数是一个 int,所以不会发生转换。左侧的操作数转换为 int。赋值的结果是 0x00000000。

由于分配,此处没有整数促销。由于~,促销活动已经发生。将发生类型更改,将int 分配给char。那不是促销。结果是char 类型。作为缩小的一部分,值 0 不会出现范围问题,并导致值为 0 并键入 char。 11...11 0000 0000 的值将通过实现定义的行为,并可能导致值 0 并且肯定键入 char

如果代码是 (x =~x) + 0,那么在添加之前,char (x =~x) 将被提升为 int

作为副作用,赋值的左侧被赋值为 0x00000000。由于 x 是 char 类型,所以还有一个隐式转换,将 0x00000000 转换为 0x00。

在前面提到过。

如果作业的结果不能存储在 char 中会怎样?

保存值是实现定义的行为。它可能包括(很少)引发异常。


位掩码和操作最好使用无符号类型和数学来处理。

【讨论】:

  • 非常明确的答案。这对我也很有帮助。谢谢。
  • 有趣的是,除了通常的 1) char 有符号或无符号 2) char 8 位或更多,3) int 编码的其他深奥问题, 4) 实现定义的窄化都可以在这样的短代码中发挥作用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-28
  • 1970-01-01
相关资源
最近更新 更多