你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

C语言版整形数相关的坑

[复制链接]
gaosmile 发布时间:2020-6-13 14:25
1、整形的存储
1)说说数据的存储
    数据对于存储器而言都是二进制的0101...,也就是我们所说的机器码。而我们所定义的类型就相当于确定了这块内存占据多大的位置和以怎么这样的方式进行解析。比如说16进制:0xFF,在unsigned char中表示255,而在signed char中表示-1,其他数据类型也是类似的道理。如果你再看得抽象一点把指针拿过来一起理解,可以把变量看成是地址固定不变的指针。
微信图片_20200613141755.png
2)聊聊原码、反码、补码
    先上理论知识注意:浮点数不是以该方式存储)
原码:
  • 正数:符号位为0,其他为正常二进制。
  • 负数:符号位为1,其他为绝对值二进制。
    3 w/ y# u/ I+ }5 y2 ^
反码:
  • 正数:与原码一致。
  • 负数:符号位为1,其他位按位取反。

    " }( B4 F" ^2 _, ?
补码:
  • 正数:与原码一致。
  • 负数:反码+1。

      l$ }  i, v, ?' c  z+ y! M
    对于整形数计算机上的存储都是以补码的形式进行存储,那么对于正数就按照正常的二进制进行存储,而负数则对原码进行反码+1存储,在我们平时进行仿真或者调试过程中用无符号输出负数会有一个非常大的数据,其实这个数据就是该负数的补码。
    比如:char类型的-3,其原码为1000 0011-->反码为11111100-->其补码为:11111101,如果我们采用unsigned char类型显示则为:253,如果我们知道原码那就反过来进行计算即可。同样其他整形数据类型也是一样的实现方式。
2、整形溢出问题
1)为什么用补码存储?
    大家应该都知道1 - 1 = 1 + (-1) = 0,那么计算机为了简化运算就把(-1)用另外一种方式存储,这样计算机就只需要进行加法运算,于是便产生了补码。同样四则运算中的乘法和除法运算都可以通过加法进行表示。(有一种"道生一、一生二、二生三、三生万物"的精妙)
微信图片_20200613141757.png
    通过上面我们也可以看出直接用原码进行计算,最终结果竟然成为了-2,明显不符合;而采用补码计算,由于采用byte计算,进位被截断了,从而获得了最终的结果0.同时使用补码的形式也规避掉了原码中0映射问题,如下图所示。
微信图片_20200613141801.png
2)数据溢出问题
    这里我以char和unsigned char类型来进行说明,对于其他整形数据同样分析即可,首选我们来看看使用补码以后的数据表示范围问题,目前最经典的图形表示方法就是采用环形表示,如下图:
微信图片_20200613141804.png
    这样表示的好处是,一旦数据溢出,直接顺着变化的方向即可找到对应的值。这里也贴出实验的代码如下:
1#include <stdio.h>
( V& E9 v: F7 T* Y# ~  l2 D+ r 2#include <stdlib.h>* L# f6 G" E- J! t. [! T$ ]
3/********************************************************
4 e. e% e9 Z# [" C 4 * Fuction:测量char类型数据溢出问题 9 E) U0 E, _0 ^! {4 s, S
5 * Author 公众号:最后一个bug) 6 `9 x' e! z& h- r9 J  I- h
6 *******************************************************/

" Q7 K4 s" ]  h9 C2 Z9 t 7int main(int argc, char *argv[]) {3 l5 i( z' a2 X; @) ?
8    char Val           = 5;: b9 F; Z% u! m7 R
9    char Val1          = 123;
7 ^% j8 p" K- L3 D. R; J! Z10    unsigned char Val2 = 5;$ ?0 U, Z) o; f1 {: V
11    unsigned char Val3 = 123;4 _* [( y& X2 w2 h" W% g
12    int i = 0;
2 o9 x( y0 j8 u  s13. M; q' R9 c& ~. r+ K9 |
14    printf("char    |     char    |     uchar   |     uchar\n");/ b, E6 o9 s  u
15    printf("-----------------------------------------------\n");        " v& u* M" j  Y5 @1 p
16    for(i = 0;i < 9;i++)
# N- U2 {7 R& A8 [5 |7 |5 n17    {5 T. e9 n8 Y! o' D0 M- M
18        printf("%4d  ******  ",--Val);  
; e+ n' Y& F$ |* [19        printf("%4d  ******  ",++Val1);
1 |4 ~$ ^' X' {( Q# n+ e20        printf("%4d  ******  ",--Val2);
2 H3 h* y4 _4 [5 W21        printf("%4d\n",++Val3);& O- I5 }1 v9 J, H/ E4 `
22    }
( k2 {8 U7 D; b) Q1 A7 Y23    printf("\n公众号:最后一个bug\n"); 6 t" ^9 V) D. v" V- S8 h* m, v
24}
    最终输出的结果与我们的环形结构是相符合的,结果如下:
微信图片_20200613141807.png
3)它来了!!!
    提个几个问,有符号char类型中的-128取相反数会获得什么值?无符号取相反数又等于什么呢?不防敲个代码实验下,代码简单直接上结果:
微信图片_20200613141810.png
    我们可以得出结论:相反数直接关于环形对称。同样其他的数据类型也是同样的性质,仅仅只是数据范围变大了。

- y" v2 \! {. e* A$ m4 e
3、算数转化
1)算数转化(前方高能)  
    首先我们来看一下一段简单的代码:(前方高能!!)1 H! _+ l. h! k6 j
1#include <stdio.h>
; t5 L* T/ c- ` 2#include <stdlib.h>
# v, W! Z) ?9 B 3/********************************************************
8 E) Z- F$ W7 b( D4 z 4 * Fuction: 算数转化测试; u# H6 t5 ]" {5 o) f
5 * Author 公众号:最后一个bug)
! V* b& l1 r# D+ N6 ?( a  t8 Q; e 6 *******************************************************/
- d. q! k6 {& s# {
7int main(int argc, char *argv[]) {
$ @  j% }2 f$ C7 Q8 p( P1 J 8    int Val1 = -2;2 B  A% C0 ~$ f- K9 W5 X1 R. @
9    unsigned int  Val2 = 1;5 A% n% `& [& z/ f
10
( |( k8 ]0 o$ t/ G' K! S  `11    if(Val1 > Val2)- I! O7 M( F& N5 S% ~- B6 `
12    {
* V" }0 w' a* p" Y$ B7 {% A13        printf("-2 > 1\n");     
% M+ G, |  |! Z14    }, I7 ~8 n" T9 k- u8 o
15    else& Q; I  Q+ p* l6 `0 R7 d
16    {
2 P  d/ X) C4 {  M- O$ p7 g; p* H17        printf("-2 < 1\n");     , s, w$ c# }' k- t# t
18    }0 k3 t5 d4 R* w
19    printf("\n公众号:最后一个bug\n"); 4 k, s  Q" g9 E8 Y( }! `
20}
    了解算数转化概念的小伙伴应该都知道,该程序并不会输出我们常规的-2 < 1,而是输出-2 > 1这个结果,这个与我们的常规结果有点不符合。(眼见为实,下图看结果)
微信图片_20200613141813.png
2)汇编来坐镇  
    作者第一个接触到这个问题的时候都怀疑人生了,这C也太坑了,一言不合就把我给弄得团团转。既然C这样做肯定有其原因吧,分析疑难杂症从汇编做起DevC++,gcc-32bit)
6 v7 o, x, i! j. p4 h+ y+ j
微信图片_20200613141816.png
    我们看到if语句对应的汇编cmp指令和jbe跳转指令;其中jbe用于判断无符号跳转指令,那么会把EAX直接当成无符号类型进行处理,从而得到了我们上述的结果,如果你把上面的代码Val2改成int类型,然后查看汇编文件会得到如下结果:
微信图片_20200613141819.png
    其中唯一的区别就是jle,该汇编指令为有符号条件转移指令,你可以编译一下能够得到我们想要的结果。
3)算数转化总结
    我们这里所说的算数转化其实就是一种隐式的强制类型转化,我们平时大部分都是使用的显示强制类型转化,就像我们上面的程序,其实这种情况是比较危险的,我们大部分汇编指令都是具有相同类型操作数,那么如果操作数类型不同,系统会根据数据类型的优先级进行自动转化。(大家可以参考下面的类型进行对应处理)
微信图片_20200613141822.png
    原则:数据都是优先转化为长数据类型,浮点与整形优先转化为浮点运算,无符号与有符号优先转化为无符号。

. X! m8 X/ p& G7 u
4、整形数据的提升
    经常有很多小伙伴把整形提升与算数转化混合一起谈,其实算数转化是为了让操作数一致而进行的隐式类型转化,而整形提升是对于短类型转化为长类型进行处理的一种方式,这个是必然的过程,不管类型是否一致。: D% n1 b2 d4 b8 X
1#include <stdio.h>6 q4 s; V, {+ ^" E6 I( m
2#include <stdlib.h>% G  O0 s; D) P8 E  S
3/********************************************************
5 N' I. |) I9 @! h) x  H 4 * Fuction:整形提升 ' ~# G3 P9 Q  [$ _
5 * Author 公众号:最后一个bug) / X* N8 E8 \9 s, R% q
6 *******************************************************/
; J# b; f6 l0 j6 `" H8 q0 h
7int main(int argc, char *argv[]) {& c1 i5 a: W# j  Q+ s2 C! Y
8    char          Val1 = 1;! Q2 f! Q$ m/ C8 b: N3 W
9    unsigned char Val2 = 2;
+ H  D- h/ x/ |% _3 _102 `# f* z7 }" e6 w
11    printf("sizeof(Val1)        = %d\n",sizeof(Val1));
+ V1 l5 k+ f( I1 }12    printf("sizeof(-Val2)       = %d\n",sizeof(-Val1)); ' M0 o6 P8 Z! x3 ]
13    printf("sizeof(Val2 - Val1) = %d\n",sizeof(Val2 - Val1));
2 v7 q, A5 y0 {# Q  M5 F! t14    printf("\n公众号:最后一个bug\n");
7 C+ V3 `# G% @15}
    最终输出的结果:
微信图片_20200613141825.png
解析一下:
    对于整形提升,其实主要是为了增加CPU运算效率,就跟我们前面说补码一样,CPU只想用加法就能够实现4则运算,那么其处理数据也是一样的,大部分的寄存器都是32位的(仅仅对于32位机器),比如上面汇编中的EAX寄存器,CPU就想直接处理32位的数,并且效率也高,计算完毕以后再转化为对应的类型获得最后的结果,对于char等等这些短数据类型在进行运算或者比较的过程中都会采用int类型进行处理,如果有更加长的数据类型会优先转化为更长的数据类型。
6 n5 |  r# Y: U5 w$ d7 Q
5、最后小结
    估计大家看完以后都不敢随便写代码了,其实不要慌,在编码的过程中一定要对每个变量的范围和变化都要了然如胸,在进行运算操作的时候最好是相同类型,切记最好不要将有符号和无符合混合使用,如果硬要混合记得显式强制类型转换。
$ Y- g. Y/ c6 Q1 P* S
赞 收藏 评论2 发布时间:2020-6-13 14:25

举报

2个回答
李康1202 回答时间:2020-6-14 14:48:58
谢谢分享
desk1983 回答时间:2020-6-16 22:40:42
, R: |5 @+ j0 k) A+ [
非常好的资料,对初学者很有帮助;' S6 I( _: ~( a6 C9 }9 ~
希望楼主多多分享,赠人玫瑰,手有余香,念念不忘,必有回响;.
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版