深度剖析数据在内存中的存储、运算

一、隐式类型转换

1.整型提升

(1)定义

C的整型算术运算总是至少以缺省整型类型的精度来进行的。

为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。

(2)意义

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

(3)操作

整型提升是按照变量二进制(补码)的符号位来提升的,即负数按1提升,正数按0提升。无符号数按0提升。

如:      char c1 = -1;

           变量c1的二进制位(补码)中只有8个比特位: 1111111 。 整形提升的时候,高位补充符号位,即为1 提升之后的结果是: 11111111 11111111 11111111 11111111

2.算术转换

如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换:

long double

double

float

unsigned long (int)

long (int)

unsigned int

int

如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。

特别地,在将排名较高的浮点型赋值给排名较低的整型时,会直接将浮点型的小数部分舍去。如:

float f = 3.78;

int num = f;

此时,num的值为3。

二、大小端存储

1.定义

大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址 中;

小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地 址中。

注:这里的"位"是以字节为单位的,因此又叫大(小)端字节序存储

2.原因

为什么会有大小端模式之分呢?这是因为在计算机系统中,我们是以字节为单位的,每个地址单元 都对应着一个字节,一个字节为8 bit。但是在C语言中除了8 bit的char之外,还有16 bit的short 型,32 bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32 位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因 此就导致了大端存储模式和小端存储模式。

例如:一个 16bit(两个字节) 的 short 型 x ,在内存中的地址为 0x0010 , x 的值为 0x1122 ,那么 0x11 为 高字节, 0x22 为低字节(0x表示十六进制,两个十六进制位表示一个字节)。对于大端模式,就将 0x11 放在低地址中,即 0x0010 中, 0x22 放在高 地址中,即 0x0011 中。小端模式,刚好相反。我们常用的 X86 结构是小端模式,而 KEIL C51 则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。

三、整型在内存中的存储

数据在计算机中都是以二进制的形式存在的。

计算机中的整数有3种二进制表示方法,即原码、反码和补码。

整型分为有符号数(signed int)和无符号数(unsigned int),有符号数又分为正数和负数。

对于无符号数和正数,  原、反、补码都相同,  直接将数值按照正负数的形式翻译成二进制就可以得到原码。

对于负数,  直接将数值按照正负数的形式翻译成二进制就可以得到原码。原码的符号位不变,  其他位按位取反就可以得到反码。反码+1就得到补码

例如:-1的原码为1000000 00000000 00000000 00000001

               反码为11111111 111111111 111111111 111111110

               补码为11111111 111111111 111111111 111111111

整型在内存中存放的都是补码。

为什么呢?在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统 一处理; 同时,加法和减法也可以统一处理(CPU只有加法器)。

此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。

(tips:原码转换成补码,  先取反,  再加1。

        补码转换成原码,  有两种方法:①先取反,  再加1     ②先减1,  再取反。  实际上,由于第一种方法与原码转成补码的方法相同,  因此计算机采用的都是这种方法)

四、浮点型在内存中的存储

根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式: (-1)^S * M * 2^E

(-1)^S表示符号位,当S=0,V为正数;当S=1,V为负数。

M表示有效数字,大于等于1,小于2。

2^E表示指数位,  E为小数点移动的位数。

例如:十进制的5.5,对应的二进制为101.1(十分位的权重为0.5),  可以表示为(-1)^0 * 1.011 * 2^2,  其中,  S=0,M=1.011,E=2。

IEEE 754规定:

对于32位的浮点数(float),最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M

 对于64位的浮点数(double),最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M

在存储时还有一些特别规定:

  • 前面说过, 1≤M<2 ,也就是说,M可以写成 1.xxxxxx 的形式,其中xxxxxx表示小数部分。 IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的 xxxxxx部分。比如保存1.01的时 候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以32位 浮点数为例,留给M只有23位, 将第一位的1舍去以后,等于可以保存24位有效数字。
  • 至于指数E,情况就比较复杂。 首先,E为一个无符号整数(unsigned int) 这意味着,如果E为8位,它的取值范围为0~255;如果E为11位,它的取值范围为0~2047。但是,我们 知道,科学计数法中的E是可以出 现负数的,所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间 数是1023。比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即 10001001。

当把浮点数从内存中取出时,  又分为3种情况:

  1. E不全为0或不全为1。 这时,只要把存储的过程逆过来就可以了,即指数E的存储值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1。 比如:浮点型二进制的表示形式为0 01111110 00000000000000000000000 ,01111110转换成十进制126,因此E的真实值为126-127= -1;M部分存放的是0,补齐前面的'1.',因此M=1.0;S=0。所以这个二进制表示的是(-1)^0*1.0*2^(-1)=0.5
  2. E全为0。这时,E的真实值为1-127(或1-1023);有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于 0的很小的数字。
  3. E全为1。这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位S)。

五、综合应用

1.整型

tips:对于整型,在内存中存放的都是补码,操作的也都是补码!

       先进行隐式类型转换,再进行计算。

  1. #include <stdio.h>
    int main()
    {
        char a = -128;
        printf("%u\n",a);
        return 0;
    }
    

    表达式a为char类型,首先进行整型提升:-128的补码为10000000, 符号位为1,整型提升为11111111 11111111 11111111 10000000。 要以%u即无符号整型的形式打印,因此所有二进制位均为数值位,原码等于补码,输出对应的十进制数4294967168

  2. int main()
    {
        char a = 128;
        printf("%u\n",a);
        return 0;
    }

    char类型能够表示的范围为-128~127,128比127大1,因此a中存放的其实是-128,(其底层逻辑为:128本来为int 型,其补码为01111111 11111111 11111111 10000000,将其存入只有一个字节的a中时,截取低八位10000000进行存放,而这正是-128的补码!) 然后的步骤与上一例相同,因此依然输出4294967168

  3. int i= -20;
    unsigned  int  j = 10;
    printf("%d\n", i+j);

    首先将int型的i转换为unsigned int:-20的补码为11111111 11111111 11111111 11101100,要将其转换为无符号数, 则所有位均视为数值位,原码等于补码。然后计算i+j: j的补码为00000000 00000000 00000000 00001010,将二者相加得11111111 11111111 11111111 11110110, 本来i+j的结果依然为无符号数,但是要以%d即有符号数的形式打印,最高位为1,是负数的补码,因此转换为对应的原码10000000 00000000 00000000 00001010并以十进制-10输出。

2.浮点型

int main()
{
 int n = 9;
 float *pFloat = (float *)&n;
 printf("*pFloat的值为:%f\n",*pFloat);

 *pFloat = 9.0;
 printf("num的值为:%d\n",n);
 return 0;
}

第一个输出0.000000:n中存放的二进制为00000000 00000000 00000000 00001001,而pFloat认为存放的是浮点数,则结构为0 00000000 00000000000000000001001,S=0; 存放E的部分全为0,所以就写成了:V=(-1)^0 × 0.00000000000000000001001×2^(-126)=1.001×2^(-146)。显然,V是一个很小的接近于0的正数,所以用十进制小数表示就是0.000000

第二个输出1091567616: 9.0可以写成(-1)^0×1.001×2^3,所以存放时S=0,E=3+127=130(二进制为10000010),M=1.001(只存001):0 10000010 00100000000000000000000,把它看成int型时,最高位为0,原码与补码相同,因此直接转化为十进制1091567616

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值