整理C++常用整数运算的所有细节

前段时间写我的安全整数类checked,顺便就通读了标准中关于整数运算的部分,还发现了不少坑,以及自己没有了解的细节,这里就总结一下。要注意的是,C和C++在这部分的逻辑不相同

第一步,单操作数的类型提升(integral promotion)

  • 对于C++中的所有字符类型,char,signed char,unsigned char,wchar_t,char16_t,char32_t,和所有小于等于int的类型,比如short,unsigned short,他们会被提升为对应的整数类型,这个对应的整数类型的选取逻辑是这样的:如果源类型的值域包含于int,那么就选择int,否则依次尝试unsigned int,long,unsigned long,long long,unsigned long long。
  • bool会被转换为int,其中false=0,true=1
  • bit field和enum不讨论

所以这里要注意,整数类型提升不保证符号性不变,但是一定不会发生溢出

用C++代码获取提升后的类型可以利用decltype和单目+运算符

template<class T>
using integral_promoted_type_t = decltype(+T{});

第二步,双操作数的类型提升(usual arithmetic conversion)

  • 对于二元运算,经过了上面所述的类型提升,如果运算符左右边的类型仍不相同,那么会进行进一步的提升,使得两个操作数的类型相同,逻辑上基本就是小的类型向大的类型转换,标准引入了 conversion rank 的概念,也是为了说清楚什么是“小的类型”和“大的类型”。
  • 对于三目运算符,会对冒号两侧的操作数进行如上转换,逻辑不变
  • 移位运算符不进行本转换

这个转换是造成整数表示溢出的罪魁祸首

这个转换的结果类型可以用标准库设施 std::common_type 获取

第三步,正式进行数学运算

走过以上的类型提升步骤之后,左右操作数的类型都相同了,此时可以进行数学运算。

  • 有符号数运算溢出,是UB
  • 移位的右操作数是负数,是UB
  • 移位数>=左操作数的bit数,是UB
  • 有符号数左移,修改了符号位,是UB
  • 特别的,C++14中规定有符号左移是以相同二进制表示的无符号数进行左移,然后再转换回有符号,转换的行为依然是实现定义的
  • 有符号数右移,行为是实现定义的,用人话说就是,标准没有规定有符号数右移一定是符号扩展(但是目前所有的编译器和CPU实现都是符号扩展)
  • 有符号数进行位运算,修改了符号位,不算UB
  • A @= B等同于A = A @ B,遵循上面提到的类型提升过程
  • ++和--的运算过程等同于+=1和-=1,遵循上面提到的类型提升过程
  • bool++和++bool的结果是true,这个C++17删掉了
posted on 2017-05-06 16:38  PointerSMQ  阅读(389)  评论(0编辑  收藏  举报