C语言隐式类型转换的一个小坑

这个问题大致是这样的,本来试图写一个产生低8位为1的掩码的语句:uint32_t mask = ~((uint8_t)0);,结果发现算出的掩码是0xffffffff显然不符合预期,于是折腾检查了一番,写出对比程序如下:

:::C++
#include <stdint.h>
#include <iostream>

using namespace std;
int main()
{
  uint8_t z = 0;
  uint32_t x = ~(uint8_t)0;
  uint32_t y = (uint8_t)~0;
  cout << typeid(~(uint8_t)0).name() << endl;
  cout << typeid((uint8_t)~0).name() << endl;
  cout << x << endl;
  cout << y << endl;
}

程序的输出(macOS, Clang)是:

i
h
4294967295
255

所以很显然这两种写法之间的细微区别,导致所产生的结果并不相同。通过typeid我们可以看到其实这两个表达式的类型就是不同的,后者的h显然是表示uint8_t,那么前者估计就是int了。所以目测这里估计是规定了隐式类型转换之类的,果断去翻一下C99标准,果不其然,6.5节开头就规定如下:

C99

然后在6.5.3.3又有如下解释:

C99

所以,在这里按位取反运算符实际上是对uint8_t进行了整型提升,然后取反的运算结果也是整数类型的-1,当转换为无符号整数的时候自然就会导致所有bit都是1,而不是期望的只有最低位一个字节为1了。

所以由以上定义,我们可以引出另外一个坑,考虑如下代码:

:::C
#include<stdio.h>
#include<stdint.h>
int main(){
    printf("0x%016llx\n", (uint64_t)~0u);
    printf("0x%016llx\n", (uint64_t)~0);
}

它的输出结果是:

0x00000000ffffffff
0xffffffffffffffff

可以看到,~0u的类型是uint32_t,所以对它执行了无符号扩展;而~0的类型为int32_t,其值为-1,所以类型转换对其进行了按符号扩展,从而得到了期望的全为1的掩码。所以,在利用类型转换来生成掩码的时候,这些小坑需要特别注意,很容易就造成不期望的结果,我向这也是为什么大多数代码都采用移位运算来构造掩码的原因。

总而言之,如果对语义理解不清晰,就果断去翻相应的spec吧。

comments powered by Disqus
Published:
2016-11-07
分类:
Tag:
C7