一,整型提升
首先我们来看整型提升的概念 :
C语言中整型运算总是至少以缺省整型类型的精度来进行的。为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换成普通整型,这种转换称为整型提升。
这句话的意思就是,表达式中的字节数少于整型的类型在使用前,会被自动转换成整型类型来进行使用,举个例子:
char a = 5; char b = 127; char c = a + b;
因为char类型的大小为1byte,小于整型int的4byte,所以char类型会被转换成int类型来进行操作,这就叫做整型提升。
接下来我们了解一下整型提升的意义 :
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器ALU操作数的字节长度 一般就是int的字节长度,同时也是CPU的通用寄存器的长度。 因此,即使两个char类型的相加, 在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算 (虽然机器指令中可能有这种字节相加指令),所以,表达式中各种长度可能小于int长度的整型值 都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
我们会发现需要进行整型提升的只有char类型和short类型。接下来我们举详细例子探究里面细节 :
首先我们得知道一些知识点 :
1. 数据在进行存储时,都是以二进制的补码形式存储的,有符号类型的原码,反码,补码是一样。无符号类型的原码,反码,补码可能不同。具体为:反码为将原码的符号位不变,有效位0 1 交换,补码就是在反码加上一个1。
2. 在进行整型提升时候,遵守以下两个规则 :
2.1. 如果是无符号数,则高位直接补0;
2.2. 如果是有符号数,则高位全补符号位。
来看这段代码 :
#include <stdio.h> int main() { char a = 5; char b = 127; char c = a + b; printf("%d",c); return 0; }
这个结果应该是什么呢?
既然a和b都会转换成int类型,那么c就应该是132.
这个结果对吗? 运行以后发现输出了 -124,,这是因为什么呢?接下来我们从细细道来 :
a(char)的二进制为 :0000 0101
b(char)的二进制为 :0111 1111
- 将a转换成int类型后,得到 :0000 0000 0000 0000 0000 0000 0000 0101
- 将b转换成int类型后,得到 :0000 0000 0000 0000 0000 0000 0111 1111
- 相加以后得到 : 0000 0000 0000 0000 0000 0000 1000 0100
- 这里有一个值得注意的点:当将相加的值赋值给c时,直接截取最右边的八位,丢弃左边的二十四位。得到c(char) :1000 0100,整型提升后,( 如果是有符号数,则高位全补符号位;)
得到 :c(int):1111 1111 1111 1111 1111 1111 1000 0100 - 由于c的符号位为1,会被视作无符号类型,因为无符号类型以补码形式存储,但是我们需要的是原码,所以此时应当进行转换,反码为 :1111 1111 1111 1111 1111 1111 1000 0100
原码为:1000 000 000 000 000 000 0111 1100 - 所以此时c(int) = -124因此打印的是-124;
小于int大小的类型会进行整型提升,那大于int类型的会怎么操作呢?接下来我们看看算术转换
二,算术转换
他的定义是 :
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换成另一个操作数的类型,否则操作就无法进行。下面的层次体系成为寻常算术转换。
这段话的意思就是不同类型进行操作时需要把其中一个操作数的转换成另一个操作数的类型,这里的规则是 :
i n t − > u n s i g n e d i n t − > l o n g i n t − > u n s i g n e d l o n g i n t − > f l o a t − > d o u b l e − > l o n g d o u b l e int -> unsigned int -> long int -> unsigned long int -> float -> double -> long doubleint−>unsignedint−>longint−>unsignedlongint−>float−>double−>longdouble
按照上面的顺序,左边的类型转换成右边的类型,举个例子 :
这里int类型和double类型进行运算,输出的是一个double类型的数,可以侧面反映出,int被转换成了double类型进行运算,而不是double转换成了int。
三,问题表达式
我们来看一串代码
#include <iostream> #include <stdio.h> using namespace std; int main() { int a = 4; a = a + ++a; printf("%d", a); return 0; }
可以看到结果为10,不出意外的话,这个结果肯定能让很多人感到意外。我们通过汇编来看看怎么实现的:
00007FF7120E252B mov dword ptr [rbp+4],4
这串代码的意思是将4放在[rbp+4]里面00007FF7120E2532 mov eax,dword ptr [rbp+4]
将[rbp+4]这个地址赋给eax寄存器00007FF7120E2535 inc eax
inc是加1指令,也就是将eax里的值加1,此时的操作就是++a
00007FF7120E2537 mov dword ptr [rbp+4],eax
再将eax还给[rbp+4],此时a = 500007FF7120E253A mov eax,dword ptr [rbp+4]
再将[rbp+4]赋给eax寄存器00007FF7120E253D mov ecx,dword ptr [rbp+4]
再将[rbp+4]赋给ecx寄存器00007FF7120E2540 add ecx,eax
将eax和ecx相加得10存放在ecx里面。00007FF7120E2542 mov eax,ecx
将ecx赋给eax。00007FF7120E2544 mov dword ptr [rbp+4],eax
将eax赋给[rbp+4],[rbp+4]地址是a的地址,也就是说,此时a = 10
这就是为什么结果是10.
为什么不能是9呢?
当然可以,我们换了一种编译器运行发现,结果为9
下面的代码是《c和指针》里面出现的,这串代码令人头大,作者经过不同编译器运行发现,不同的编译器运行结果都不一样。
所以我们在平时写代码时候,要杜绝这种问题代码的出现,让代码没有二义性。
😄 创作不易,你的点赞和关注都是对我莫大的鼓励,再次感谢您的观看。😄