按位运算符和签名类型

时间:2015-01-25 21:40:59

标签: c++ bit-manipulation language-lawyer signed

我正在阅读C ++入门手册,我对一些有关Bitwise运算符如何处理签名类型的评论感到有些困惑。我引用:

引用#1

  

(当谈论按位运算符时)"如果操作数是有符号的   它的值是负的,然后处理“符号位”的方式   许多按位操作取决于机器。此外,   做一个改变符号位值的左移是   未定义"

引用#2

  

(当谈到右移操作符时)"如果该操作数是   无符号,然后运算符在左边插入0值位;如果它   是签名类型,结果是实现定义 - 副本   符号位或0值位插入左侧。"

按位运算符将小整数(例如char)提升为已签名的整数。当按位运算符经常在已签名的运算符类型上给出未定义或实现定义的行为时,此促销是否存在已签名整数的问题?为什么不将标准的char提升为unsigned int?


编辑:以下是我提出的问题,但我已将其放回上下文,并在下面给出了一些答案。

以后的练习要求

&#34; ~'q' << 6在具有32位int和8位char s的计算机上的值是多少,它使用的是Latin-1字符集,其中& #39; q&#39;有位模式01110001?&#34;

嗯,&#39; q&#39;是一个字符文字,将被提升为int,给出

~'q' == ~0000000 00000000 00000000 01110001 == 11111111 11111111 11111111 10001110

下一步是将左移位运算符应用于上面的位,但是引用#1 提及

  

&#34;做一个改变符号位值的左移是   未定义&#34;

好吧,我不知道哪个位是符号位,但答案肯定是未定义的?

3 个答案:

答案 0 :(得分:7)

您非常正确 - 根据标准,表达式~'q' << 6是未定义的行为。它比你说的更糟糕,因为~运算符被定义为计算&#34;唯一的补码&#34;值,对于有符号(2s-补码)整数没有意义 - 术语&#34;一个补码&#34;只对无符号整数有意义。

执行按位运算时,如果要严格定义(根据标准)结果,通常必须确保操作的值是无符号的。您可以使用显式强制转换或在二进制操作中使用显式无符号常量(U - 后缀)来执行此操作。使用signed和unsigned int执行二进制操作是无符号的(有符号值转换为unsigned)。

C和C ++与整数提升有细微的不同,所以你需要小心 - 在与其他操作数比较之前,C ++会将小于int的无符号值转换为int(signed)以查看应该是什么完成后,C将首先比较操作数。

答案 1 :(得分:4)

阅读标准的确切文本可能最简单,而不是像Primer Plus中的摘要。 (摘要必须通过总结来省略细节!)

相关部分是:

  

<强> [expr.shift]

     
      
  1. 轮班操作员<<>>从左到右分组。   操作数应为整数或无范围的枚举类型,并执行整数提升。结果的类型是提升的左操作数的类型。如果右操作数为负数,或者大于或等于提升左操作数的位长度,则行为未定义。

  2.   
  3. E1 << E2的值为E1左移E2位位置;空位是零填充的。如果E1具有无符号类型,则结果的值为E1×2 E2 ,比结果类型中可表示的最大值减少一个模数。否则,如果E1具有签名类型和非负值,并且E1×2 E2 可在结果类型的相应无符号类型中表示,那么转换为结果类型的那个值就是结果值;否则,行为未定义。

  4.         

    <强> [expr.unary.op] / 10

         

    ˜的操作数应具有整数或无范围的枚举类型;结果是其操作数的一个补码。执行整体促销。结果的类型是提升的操作数的类型。

请注意,这些都不执行通常的算术转换(这是转换为大多数二元运算符完成的常见类型)。

整体促销:

  

<强> [conv.prom] / 1

     

整数转换等级小于int等级的boolchar16_tchar32_twchar_t以外的整数类型的prvalue可以转换为如果int可以表示源类型的所有值,则为int类型的prvalue;否则,源prvalue可以转换为unsigned int类型的prvalue。

(“除了”列表中的类型还有其他条目,我在这里省略了它们,但你可以在标准草案中查找它。)


关于整数促销的重要事项是,如果您的char值为-30,则它们是保值,然后在促销之后它将是值int的{​​{1}}。您无需考虑“签名扩展”等内容。

您对-30的初步分析是正确的,结果的类型为~'q'(因为int可以代表普通系统上int的所有值。

事实证明,设置了最高有效位的任何char都表示负值(在此处未引用的标准的另一部分中有关于此的规则),因此int是负~'q'

查看[expr.shift] / 2,我们看到这意味着左移会导致未定义的行为(该段落中的任何早期案例都没有涵盖)。

答案 2 :(得分:2)

当然,通过编辑问题,我的回答现在部分地回答了一个不同于提出问题的问题,所以这里试图回答“新”问题:

标准中明确定义了促销规则(转换为什么)。类型char可以是signedunsigned - 在某些编译器中,您甚至可以给编译器一个标志,说“我想要unsigned char类型”或“我想要签名的char类型” “ - 但大多数编制者只是将char定义为signedunsigned

默认情况下会对6等常量进行签名。当在代码中写入'q' << 6等操作时,编译器会将任何较小的类型转换为任何较大的类型[或者如果您通常进行任何算术,char将转换为int }],因此'q'成为'q'的整数值。如果你想避免这种情况,你应该使用6u或显式强制转换,例如static_cast<unsigned>('q') << 6 - 这样,你可以确保操作数转换为无符号,而不是签名。

操作未定义,因为不同的硬件行为不同,并且存在具有“奇怪”编号系统的架构,这意味着标准委员会必须在“排除/制造操作极其低效”或“定义标准”之间进行选择。方式不太清楚“。在一些体系结构中,溢出的整数也可能是一个陷阱,如果你改变,你改变数字上的符号,这通常被视为溢出 - 并且由于陷阱通常意味着“你的代码不再运行”,这不会是你的普通程序员所期望的 - &gt;属于“未定义行为”的保护伞。大多数处理器没有,如果你这样做,也不会发生任何坏事。

旧答案: 因此,避免这种情况的解决方案是在转移它们之前始终将您的签名值(包括char)转换为无符号值(或接受您的代码可能无法在另一个编译器,具有不同选项的相同编译器或下一个版本上工作)相同的编译器)。

值得注意的是,结果值“几乎总是你所期望的”(因为编译器/处理器只会对值执行左移或右移,右移使用符号位向下移动) ,它只是未定义或实现定义,因为某些机器架构可能没有硬件“做对了”,C编译器仍然需要在这些系统上工作。

符号位是二进制补码中的最高位,您不会通过移位该数字来改变它:

       11111111 11111111 11111111 10001110 << 6 =
111111 11111111 11111111 11100011 10000000
^^^^^^--- goes away.
result=11111111 11111111 11100011 10000000 

或者作为十六进制数:0xffffe380。