您的位置:首页 > 其它

类型转换下编译器偷偷做的事————整形提升(Inter Promotion)

2017-03-03 12:55 423 查看

一.前言

在讲述这个话题之前,还是先举几个代码例子:
long long     ll   = 1;
long          l    = 2;
double        db   = 3;
float         f    = 4;
unsigned int  uint = 5;

decltype(db   + ll)       // ->类型?
decltype(uint + f)        // ->类型?
decltype(uint + l)        // ->类型?


signed char a = 0xe0;
unsigned int b = a;
unsigned char c = a;
bool b1 = (a > 0 && c > 0);         // b1 true ? false ?
bool b2 = (a == c);                 // b2 true ? false ?
printf("%08x",b);                   // what's the output ?


//小端
unsigned int a = 0xFFFFFFF7;
unsigned char i = (unsigned char)a;
char* b = (char*)&a;
printf("%08x %08x",i,*b);           //what's the output ?


二.隐式转化规则

隐式转化发生在很多地方:表达式计算、赋值、实参到形参、函数返回值等等。这里不一一介绍。这里主要是介绍表达式计算中隐式转换相关的问题,在二元操作中,如果两个操作数类型不一样就会发生隐式类型转换,而一般的转化规则如下:(从上到下,序号越小优先级越高)
1.判断是否有操作数为long double类型,如果有将另一个操作数转化为long double 类型。

2.判断是否有操作数为double类型,如果有将另一个操作数转化为double类型

3.判断是否有操作数为float类型,如果有将另一个操作数转化为float类型

4.两个操作数皆为整形,进行Inter Promotion,按照int,unsigned int,long,unsigned long,long long,unsigned long long,进行找到一个最小且能够存放两个操作数对应类型的所有值的类型。

可以看出来无论整形操作数类型字节多大,一旦有浮点数类型参与,为了保证浮点数精度,都会转化为浮点数类型。
或许,有的人在不知晓Inter Promotion提升规则之前,面对Inter Promotion有过误解:在两个整形相运算时候,一个低位宽的类型总是向另一个高位宽的类型转换,然而这是不正确的。因为在Inter Promotion的过程中隐式转换结果类型甚至可以不为两个操作数类型的任意一个。

比如,32位机器上:unsigned int + long -> unsigned long。

要正确理解上面这个转化,我们需要细细品味第四个规则的那句话:”最小且能够存放两个操作数对应类型的所有值的类型”,b类型能存放a类型所有值的意思就是对于a类型的任意一个值,b类型都能表达出且不会溢出。

所以,上面的转换中,假如我们尝试将目标类型定为long。由于我们知道在32位机上long和unsigned int都占4字节,所以很显然对于unsigned int类型中大于2^31-1的正值,long 类型都无法表达出。所以需要将目标类型提升为 unsigned long 。

那么第一个段代码的答案就很显然了:
decltype(db   + ll)       // ->db
decltype(uint + f)        // ->float
decltype(uint + l)        // ->unsigned long


三.Inter Promotion

为什么再单独将Inter Promotion拿出来讲一节,我是想再从内存的角度去简单了解一下Inter Promotion时编译器偷偷做了什么事情。

为了便于阐述,我还是将整形字面常量的情况拿出来说一下:
//type 为某整形类型
type a = 123;
a = 456;
type b = 123*123;
b = 456 * 789;


上面的整形字面常量参与了这样几个动作:初始化、赋值、运算。那么在这一系列过程中,类型发生了如何的转换?
整形字面常量直接初始化或者作为右值赋值,那么整形字面常量会直接转化为十六进制然后bitwise copy。如果整形字面常量位数和变量类型位数不匹配那么就会发生截断或者Inter Promotion

如果整形字面常量参与运算,那么会按照以下处理(不考虑负号,负号只是对原立即数做一次负数补码操作):

MSVCGNU C++
0 ~ 2147483647(2^31-1)intint
2147483648(2^31-1) ~ 4294967295(2^32-1)unsigned longlong long
4294967295(2^32-1) ~ 9.22*10^18(2^63-1)long longlong long
9.22*10^18(2^63-1) ~ 1.84*10^19(2^64-1)unsigned long long__int128
截断是指目标类型位宽小于源类型位宽,这样的情况会将源数据进行截断,只取目标类型位宽的数据,而这里截断后的结果位于源数据的高位还是低位就要根据机器存储是大端(big endian)还是小端(little endian),这里不予多着墨解释。

整形提升(Inter Promotion)则是指目标类型位宽大于源类型位宽。这样的情况除了将源类型位宽放置于目标类型的低位,还需要对目标类型的高位进行填充。而填充规则则是:

如果源类型为unsigned类型,则用0填充高位。

如果源类型为signed,则用源数据符号位的值填充高位。

依照上面的规则,我们再来看看文章最开始所提及的题目:
signed char a = 0xe0;
unsigned int b = a;
unsigned char c = a;
bool b1 = (a > 0 && c > 0);         // b1 true ? false ?
bool b2 = (a == c);                 // b2 true ? false ?
printf("%08x",b);                   // what's the output ?


b = a -> 进行类型转换,源类型位宽小于目标类型,进行整形提升。源类型为signed,那么用符号位填充目标类型高位。0xe0符号位为1,所以填充后b的十六进制为 : 0xffffffe0。

c = a -> 进行类型转换,源类型位宽和目标类型位宽一样,所以在内存中a,c的数据其实是一样的。

来看第一个:bool b1 = (a > 0 && c > 0),根据上文(二.隐式转换规则)的第4点,我们知道a,c 都会隐式提升到int,再来看整形提升的规则,我们可以知道a是signed且符号位为1,所以提升到 int 为 0xffffffe0, 而c是unsigned,所以提升到 int 为 0x000000e0。所以知道 a < 0,c > 0 。那么 b1 = false;

第二个,和第一个一样,根据上文(二.隐式转换规则)的第4点,我们知道a,c 都会隐式提升到int,a : 0xffffffe0 ,c : 0x000000e0,a != c。则b2 = false。

第三个,上面已经说了b的十六进制为 : 0xffffffe0。所以输出为 ffffffe0。
unsigned int a = 0xFFFFFFF7;
unsigned char i = (unsigned char)a;
char* b = (char*)&a;
printf("%08x %08x",i,*b);           //what's the output ?


i = (unsigned char)a,在类型转换过程中发生了截断,因为是小端存储,所以 i 的十六进制为 0xF7。

哦,这里我要先说说%x,%x的定义:使用十六进制数字0F的无符号十六进制整数x。换句话来说,它会把源数据转化为unsigned int然后再将其十六进制数据输出。

根据上面所述的规则,i是unsigned所以0填充,结果为000000f7,然而b是signed,所以用符号位填充,结果为fffffff7。

有人可能会说i,*b内存数据不是一样的吗,怎么按照同一个格式输出结果不一样?那是因为i,*b的数据符号类型不一样导致,i,*b在整形提升过程中,高位填充不一样。当然我们也可以不进行整形提升直接输出源内存十六进制值:
unsigned int a = 0xFFFFFFF7;
char i = (char)a;
char* b = (char*)&a;
printf("%hhx %hhx", i, *b);       // f7  f7


这样输出结果就都为f7了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐