c标准和位移

这个问题首先受到此代码的(意外)结果的启发:

uint16_t t16 = 0; uint8_t t8 = 0x80; uint8_t t8_res; t16 = (t8 << 1); t8_res = (t8 << 1); printf("t16: %x\n", t16); // Expect 0, get 0x100 printf(" t8: %x\n", t8_res); // Expect 0, get 0 

但事实certificate这是有道理的:

6.5.7按位移位运算符

约束

2每个操作数应具有整数类型

因此最初混淆的线相当于:

 t16 = (uint16_t) (((int) t8) << 1); 

有点不直观的恕我直言,但至少定义明确。

好的,很棒,但接下来我们做了:

 { uint64_t t64 = 1; t64 <<= 31; printf("t64: %lx\n", t64); // Expect 0x80000000, get 0x80000000 t64 <<= 31; printf("t64: %lx\n", t64); // Expect 0x0, get 0x4000000000000000 } 

// edit:遵循与上面相同的文字参数,以下内容应该是等效的:

 t64 = (uint64_t) (((int) t64) << 31); 

//因此我的困惑/期望[end_edit]

现在,我们得到了直观的结果,但不是从我(标准)阅读标准得到的结果。 何时/如何进行“进一步自动型推广”? 或者其他地方是否存在限制类型永远不会被降级(这有意义?),在这种情况下,促销规则如何适用于:

 uint32_t << uint64_t 

由于标准确实说两个参数都被提升为int; 这两个论点都应该提升到同一类型吗?

//编辑:

更具体地说,结果应该是什么:

 uint32_t t32 = 1; uint64_t t64_one = 1; uint64_t t64_res; t64_res = t32 << t64_one; 

//结束编辑

当我们认识到规范并不要求对int特定的提升,而是要求uint64_t有资格的integer type ,上述问题的答案就解决了。

//澄清编辑:

好的,但现在我又困惑了。 具体来说,如果uint8_t是一个整数类型,那为什么它被提升为int呢? 它似乎与常量int 1无关,如下面的练习所示:

 { uint16_t t16 = 0; uint8_t t8 = 0x80; uint8_t t8_one = 1; uint8_t t8_res; t16 = (t8 << t8_one); t8_res = (t8 << t8_one); printf("t16: %x\n", t16); printf(" t8: %x\n", t8_res); } t16: 100 t8: 0 

如果uint8_t是整数类型,为什么会提升(t8 << t8_one)表达式?

作为参考,我的工作是ISO / IEC 9899:TC9,WG14 / N1124 2005年5月6日。如果这已经过时,有人也可以提供更新版本的链接,那也值得赞赏。

§6.5.7中的约束“每个操作数应具有整数类型”。 是一个约束,意味着您不能在非整数类型(如浮点值或指针)上使用按位移位运算符。 它不会导致您注意到的效果。

导致效果的部分在下一段中:

3.对每个操作数执行整数提升。 结果的类型是提升的左操作数的类型。

整数提升在第6.3.1.1节中描述:

2.如果可以使用intunsigned int则可以在表达式中使用以下内容:

  • 具有整数类型的对象或表达式,其整数转换等级小于或等于intunsigned int的等级。
  • _Boolintsigned intunsigned int类型的位字段。

如果int可以表示原始类型的所有值,则该值将转换为int ; 否则,它将转换为unsigned int 。 这些被称为整数促销 。 整数促销不会更改所有其他类型。

uint8_t的排名小于int ,因此该值被转换为int (因为我们知道int必须能够表示uint8_t所有值,给定对这两种类型的范围的要求)。

排名规则很复杂,但它们保证排名较高的类型不能具有较低的精度。 实际上,这意味着不能通过整数提升将类型“降级”为精度较低的类型( uint64_t可以提升为intunsigned int ,但当类型的范围至少为of uint64_t )。

uint32_t << uint64_t的情况下,启动的规则是“结果的类型是提升的左操作数的类型” 。 所以我们有几个可能性:

  • 如果int至少为33位,那么uint32_t将被提升为int ,结果将为int ;
  • 如果int小于33位且unsigned int至少为32位,那么uint32_t将被提升为unsigned int ,结果将是unsigned int ;
  • 如果unsigned int小于32位,则uint32_t将保持不变,结果将为uint32_t

在今天常见的桌面和服务器实现中, intunsigned int通常是32位,因此第二种可能性将发生( uint32_t被提升为unsigned int )。 在过去, int / unsigned int通常是16位,第三种可能性( uint32_t未启动)。

你的例子的结果:

 uint32_t t32 = 1; uint64_t t64_one = 1; uint64_t t64_res; t64_res = t32 << t64_one; 

将值2存储到t64_res 。 请注意,这不受表达式结果不是uint64_t这一事实的影响 - 并且受影响的表达式的示例是:

 uint32_t t32 = 0xFF000; uint64_t t64_shift = 16; uint64_t t64_res; t64_res = t32 << t64_shift; 

这里的结果是0xf0000000

请注意,尽管细节相当复杂,但您可以将其全部归结为一个相当简单的规则,您应该记住:

在C中,算术永远不会在比int / unsigned int更窄的类型中完成。

我认为你混淆的根源可能是以下两个陈述相同:

  • 每个操作数应具有整数类型
  • 每个操作数都应具有int类型

uint64_t是整数类型。

你在标准中找到了错误的规则:(相关的东西就像“通常的整数类型促销适用”。这就是第一个例子。如果像uint8_t这样的整数类型的排名小于int它是提升为uint64_t没有小于intunsigned的等级,因此不执行任何提升,并且<<运算符应用于uint64_t变量。

编辑:提升所有小于int整数类型以进行算术运算。 这只是生活中的事实:) uint32_t是否被提升取决于平台,因为它可能具有相同的等级或高于int (未提升)或更小的等级(提升)。

关于<<运算符,右运算数的类型并不重要,对于位数的计数是左数(具有上述规则)。 对于正确的一个更重要的是它的价值。 它不是负数或超过(提升的)左操作数的宽度。