当前位置:   article > 正文

15.75.【C语言】表达式求值

15.75.【C语言】表达式求值

目录

一.整型提升

1.定义

2.

一.整型提升

1.定义

C语言中整型算术运算总是至少以缺省(默认)整型类型的精度来进行的。为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升

2.整型提升的原因:

表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。

  1. char a;//char本质上是存储的是ASCII值
  2. short b;

char,short-->int 或 unsigned int

3.方法:

*有符号整数提按照变量的数据类型的符号位来提升至int的位数

char a;-->signed char a;

+5原码=反码=补码=00000000 00000000 00000000 00000101 (作int时)(从8bit-->32bit)

由于char只能存8 bit,所以变量里存储00000000 00000000 00000000 00000101,称为整型截断:一个字节大的整型数据赋值给一个字节小的整型变量时,发生的数据丢失

*无符号整数提升,高位补0至int的位数

 练习:求打印的值

  1. #include <stdio.h>
  2. int main()
  3. {
  4. char a=5;
  5. char b=126;
  6. char c=a+b;
  7. printf("%d",c);
  8. }

分析:计算(a+b)前char整型提升至int

5:      0000000 00000000 00000000 00000101

126:    00000000 00000000 00000000 01111110

5+126: 00000000 00000000 0000000 10000011

存储至c时,整型截断:00000000 00000000 0000000 100000011

所以c的二进制序列为100000011,c的十进制序列为131,但打印的结果不是131

要充分理解:字符和短整型操作数使用之前被转换为普通整型的含义,printf也算使用!

解释1:

VS2022中char默认按signed char处理,最高位是1,所以高位补1

补码:11111111 11111111 11111111 10000011

符号位不变,其余各位取反再+1:10000000 00000000 00000000 01111101-->-125

解释2:“环绕溢出”

由于8bit存储有符号整数范围-128~+127

所以画图:


练习:代码修改后求打印的值

  1. #include <stdio.h>
  2. int main()
  3. {
  4. unsigned char a = 5;
  5. unsigned char b = 126;
  6. unsigned char c = a + b;
  7. printf("%d", c);
  8. }

分析指定无符号(unsigned char)整数,范围是0~2的8次方-1即255,显然131<255,所以打印131

二.算术转换

1.定义

如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行

2.剖析

  1. float a;
  2. double b;
  3. a+b;

a+b,a是float,b是double,两者类型不一样,VS会把float转换为double,执行a+b;具体原因参见第3点

3.寻常算术转换

  1. 1. long double
  2. 2. double
  3. 3. float
  4. 4. unsigned long int
  5. 5. long int
  6. 6. unsigned int
  7. 7. int

规则:序号大的转换为序号小的

序号3是float,序号2是double,2<3,所以把float转换为double

4.问题表达式例子分析

表达式的执行看优先级(回顾15.25【C语言】操作符的属性),而且表达式真正计算的时候先看相邻操作符的优先级再决定先算谁,但有了优先级就一定能确定唯一的运算顺序吗?

a*b + c*d + e*f

执行顺序有两种可能:

1.先算完*,后算完+

2.a*b-->c*d-->a*b + c*d-->e*f-->a*b + c*d + e*f

显然不能确定唯一的运算顺序

注意:不同的运算顺序可能答案有所不同,上述a,b,c,d,e,f可以是变量,也可以是表达式


  1. int c=3;
  2. int b=c + --c;

查优先级可知,先--后+ ,但+号两边都含c,+的左操作数在--c之前还是之后准备好的,无从得知,建议再设一个变量


出自《C和指针》

  1. int main()
  2. {
  3. int i = 10;
  4. i = i-- - --i * ( i = -3 ) * i++ + ++i;
  5. printf("i = %d\n", i);
  6. return 0;
  7. }

理由和上方解释思想一样 ,不同编译器解释出来的答案不一样


下列代码的输出结果是否可求?

  1. #include <stdio.h>
  2. int fun()
  3. {
  4. static int count = 1;
  5. return ++count;
  6. }
  7. int main()
  8. {
  9. int answer=0;
  10. answer = fun() - fun() * fun();
  11. printf( "%d\n", answer);
  12. return 0;
  13. }

注意到 static int 回顾17.【C语言】初识常见关键字 下

摘取:

int a=1;等价于auto int a=1;

static int a=1;修饰局部变量,a不会自动销毁,生命周期变长

总结:static修饰局部变量时改变了变量的存储类型,进而改变了局部变量的生命周期

count会从1到4而且函数的调用先后顺序无法通过操作符的优先级确定


  1. #include <stdio.h>
  2. int main()
  3. {
  4. int i = 1;
  5. int result = (++i) + (++i) + (++i);
  6. printf("%d\n", result);
  7. printf("%d\n", i);
  8. return 0;
  9. }

Debug x86环境下编译 ,反汇编

  1. #include <stdio.h>
  2. int main()
  3. {
  4. 006B1870 push ebp
  5. 006B1871 mov ebp,esp
  6. 006B1873 sub esp,0D8h
  7. 006B1879 push ebx
  8. 006B187A push esi
  9. 006B187B push edi
  10. 006B187C lea edi,[ebp-18h]
  11. 006B187F mov ecx,6
  12. 006B1884 mov eax,0CCCCCCCCh
  13. 006B1889 rep stos dword ptr es:[edi]
  14. 006B188B mov ecx,offset _2D923C74_FileName@c (06BC008h)
  15. 006B1890 call @__CheckForDebuggerJustMyCode@4 (06B132Ah)
  16. 006B1895 nop
  17. int i = 1;
  18. 006B1896 mov dword ptr [i],1
  19. int result = (++i) + (++i) + (++i);
  20. 006B189D mov eax,dword ptr [i]
  21. 006B18A0 add eax,1
  22. 006B18A3 mov dword ptr [i],eax
  23. 006B18A6 mov ecx,dword ptr [i]
  24. 006B18A9 add ecx,1
  25. 006B18AC mov dword ptr [i],ecx
  26. 006B18AF mov edx,dword ptr [i]
  27. 006B18B2 add edx,1
  28. 006B18B5 mov dword ptr [i],edx
  29. 006B18B8 mov eax,dword ptr [i]
  30. 006B18BB add eax,dword ptr [i]
  31. 006B18BE add eax,dword ptr [i]
  32. 006B18C1 mov dword ptr [result],eax
  33. printf("%d\n", result);
  34. 006B18C4 mov eax,dword ptr [result]
  35. 006B18C7 push eax
  36. 006B18C8 push offset string "%d\n" (06B7B30h)
  37. 006B18CD call _printf (06B10D2h)
  38. 006B18D2 add esp,8
  39. printf("%d\n", i);
  40. 006B18D5 mov eax,dword ptr [i]
  41. 006B18D8 push eax
  42. 006B18D9 push offset string "%d\n" (06B7B30h)
  43. 006B18DE call _printf (06B10D2h)
  44. 006B18E3 add esp,8
  45. return 0;
  46. 006B18E6 xor eax,eax
  47. }
  48. 006B18E8 pop edi
  49. 006B18E9 pop esi
  50. 006B18EA pop ebx
  51. 006B18EB add esp,0D8h
  52. 006B18F1 cmp ebp,esp
  53. 006B18F3 call __RTC_CheckEsp (06B124Eh)
  54. 006B18F8 mov esp,ebp
  55. 006B18FA pop ebp
  56. 006B18FB ret

int i = 1;以上的汇编指令是栈区的初始化,具体分析见36.【C语言】函数栈帧的创建和销毁

重点分析int i=1;和int result这行代码

总结:即使有了操作符的优先级和结合性,我们写出的表达式依然有可能不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在潜在风险的,建议不要写出特别复杂的表达式

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/951985
推荐阅读
相关标签
  

闽ICP备14008679号