数据在内存中的存储

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文

目录

一、数据类型详细介绍

1.1 C语言内置数据类型

1.2 数据类型分类

二、整型在内存中的存储

2.1 原码、反码、补码

三、大小端字节序介绍及判断

3.1 大小端是什么

3.2  判断是大端存储还是小端存储

3.3 整型提升

3.4 练习

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

4.1 浮点数存储规则

4.2 练习

一、数据类型详细介绍

1.1C语言内置数据类型: 

 1.2数据类型分类:

二、整型在内存中的存储

2.1原码、反码、补码

三、大小端字节序介绍及判断 

3.1大小端是什么

3.2判断是大端存储还是小端存储

3.3整型提升

3.4练习

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

4.1浮点数存储规则

4.2练习


一、数据类型详细介绍

1.1C语言内置数据类型: 

 类型的意义:

1. 使用这个类型开辟内存空间的大小(大小决定了使用范围);

2.如何看待内存空间的视角 。 

 1.2数据类型分类:

整型家族:

为什么char类型会属于整型呢?

原因是:字符的本质是ASCII值,char在内存中存的是对应字符的ASCII是整型,因此char归为整型,此外,char本身究竟是unsigned char还是signed char 取决于编译器,不同编译器可能不同。

 浮点型家族:

构造类型:

指针类型:

空类型: 

 

二、整型在内存中的存储

2.1原码、反码、补码

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

       三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位 正数的原、反、补码都相同。

负整数的三种表示方法各不相同:

 原码: 直接将数值按照正负数的形式翻译成二进制就可以得到原码;

 反码: 将原码的符号位不变,其他位依次按位取反就可以得到反码;

 补码: 反码+1就得到补码。

对于整型来说:数据在内存中存储的是补码

为什么是存储补码:原因在于,使用补码,可以将符号位和数值域统 一处理; 同时,加法和减法也可以统一处理(CPU只有加法器);

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

        为什么不需要额外硬件电路:补码取反加1可以得到原码,原码取反加1也可以得到补码,这个运算过程是一样的,因此不需要额外的硬件电路。


三、大小端字节序介绍及判断 

我们可以通过调试起来之后在内存监视窗口看到数据存储的情况,下图是变量a和b在内存中存储的顺序。

 通过图片我们可以看出,a和b在内存中存储的是补码,但是顺序却“不太对”,顺序是反着的,

这里就涉及到了大小端的内容。

3.1大小端是什么

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

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

为什么会有大小端:

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

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

3.2判断是大端存储还是小端存储

#include <stdio.h>
int check_sys()
{
 int i = 1;
 return (*(char *)&i);
}
int main()
{
 int ret = check_sys();
 if(ret == 1)
 {
 printf("小端\n");
 }
 else
 {
 printf("大端\n");
 }
 return 0;
}

        在这里我们设计一个函数来判断我们的计算机是大端存储还是小端存储,我们知道,小端存储的话就是低位存在低地址,高位存在高地址,大端反之。

       因此我们就可以通过判断一个数据在内存中的第一个字节存储的值到底是数据的高位还是低位来判断大小端。

       实现:这个函数内部我们就先定义一个变量i并赋值1(1判断比较方便),1在内存中写成十六进制是0x00000001,然后我们取出i的地址,其实我们只需要i地址中的的第一个字节的值,因此可以强制类型转换成char*类型的指针并解引用,就可以成功拿到第一个字节的数据,如果是小端存储那么拿到的就是低位的值,就是1,如果是大端存储,拿到的就是高位的值就是0。所以可以直接通过函数返回值判断。

3.3整型提升

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

整型提升的意义:

       表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度 一般就是int的字节长度,同时也是CPU的通用寄存器的长度。因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。

       通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转 换为int或unsigned int,然后才能送入CPU去执行运算

整型提升的规则: 

       1.char,short类型在进行运算或者以整型的类型进行输出时,计算机都会对它们进行整型提升,然后再运算

       2.有符号位的整型提升 ---- 从符号位开始往左补二进制数,二进制数的值与符号位相同,补到总二进制数为32位为止

       3.无符号位 --- 从已有的最高位向左开始补0,补到32位为止

       4.计算机存一个整型的时候存的是它的补码(正数:都一样,负数:算术转换),而当计算机要输出这个整型的时候会 1.补码转成原码 2.将原码转为对应的十进制数输出.

3.4练习

练习1. 

#include <stdio.h>
int main()
{
    char a= -1;
    signed char b=-1;
    unsigned char c=-1;
    printf("a=%d,b=%d,c=%d",a,b,c);
    return 0;
}

 输出:

为什么是这个输出呢?下面我们就来解释一下:

    首先,-1是一个整数,在内存中是是补码形式存储的,可以先写出-1的原反补,a的原码:10000000000000000000000000000001 反码:11111111111111111111111111111110

补码:11111111111111111111111111111111,因为char类型只占1个字节就是8个bit位,所以会发生截断,截断后保留低位:11111111这是补码形式,通过输出我们可以知道在vs2019的编译器下char是相当于signed char的,在这个程序中输出是以整型的%d十进制输出的,所以在这里会发生整型提升,所以提升后就是11111111111111111111111111111111(补码形式),转换成原码就是10000000000000000000000000000001 ,所以打印出来就是-1,而b与a是一样的,所以也是-1。

       而c在内存中存储还是11111111,因为还是以整型%d十进制的形式打印的,又因为c是unsigned char类型的,所以整型提升高位补0,提升后就是:00000000000000000000000011111111,unsigned类型的就是正数,正数原码反码补码相同,所以就是255.

练习2.


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

 输出:

 接下来我们来分析一下,为什么会输出这么大的一个值:

%u 是打印无符号整形,认为内存中存放的补码对应的是一个无符号数;

首先-128是一个整数的原码是:10000000000000000000000010000000,反码:11111111111111111111111101111111,补码是:11111111111111111111111110000000

因为char类型只占1个字节所以会发生截断:截断会保留低位就是:10000000,

因为要按%u的形式打印,%u是打印无符号整型,认为内存中是一个无符号数,打印时会先进行整型提升,a是char类型的,是有符号型所以整型提升为11111111111111111111111110000000,而%u打印会认为内存中是无符号数,直接以十进制形式打印出来,所以结果就是4294967168.

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

4.1浮点数存储规则

        根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式: (-1)^S * M * 2^E (-1)^S表示符号位,当S=0,V为正数;当S=1,V为负数。M表示有效数字,大于等于1,小于2。 2^E表示指数位

举例来说:

     十进制的5.0,写成二进制是 101.0 ,相当于 1.01×2^2 。 那么,按照上面V的格式,可以得出S=0,M=1.01,E=2。 十进制的-5.0,写成二进制是 -101.0 ,相当于 -1.01×2^2 。那么,S=1,M=1.01,E=2。

IEEE 754规定:

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

float型:

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

double型:

       IEEE 754对有效数字M和指数E,还有一些特别规定。 前面说过, 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。  00000000000000000000000

指数E可以分为三种情况:

1. E不全为0或不全为1

       这时,浮点数就采用下面的规则表示,即指数E的计算值减去127(或1023),得到真实值将有效数字M前加上第一位的1。 比如: 0.5(1/2)的二进制形式为0.1,由于规定正数部分必须1,即将小数点右移1位,则为 1.0*2^(-1),其阶码为-1+127=126,表示为 01111110,而尾数1.0去掉整数部分为0,补齐0到23位00000000000000000000000,则其二进 制表示形式为:

0 01111110 00000000000000000000000

2.E全为0

      这时,浮点数的指数E等于1-127(或者1-1023)即为真实值, 有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于 0的很小的数字。

3.E全为1

       这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s); 好了,关于浮点数的表示规则,就说到这里。

4.2练习

一道例题:

int main()
{
 int n = 9;
 float *pFloat = (float *)&n;
 printf("n的值为:%d\n",n);
 printf("*pFloat的值为:%f\n",*pFloat);
 *pFloat = 9.0;
 printf("num的值为:%d\n",n);
 printf("*pFloat的值为:%f\n",*pFloat);
 return 0;
}

 输出结果:

 为什么会输出这样的结果呢?整型变量n在内存中存储的是0000 0000 0000 0000 0000 0000 0000 1001,而这个二进制码对于float型的数据来说是第一位符号位即S=0;接下去的八位表示指数位即E=0,再接下去为有效数字即M,由于E全为0,符合以上的第二种情况,可以写成:

V=(-1)^0 × 0.00000000000000000001001×2^(-126)=1.001×2^(-146)

 显然,V是一个很小的接近于0的正数,所以用十进制小数表示就是0.000000,所以第二个数输出为0.000000.

接着n被赋值为9.0;是浮点数,所以会按照浮点数规则进行存储。

浮点数9.0等于二进制的1001.0,即1.001×2^3。

可以写成:9.0 -> 1001.0 ->(-1)^0 * 1.001 * 2^3 -> s=0, M=1.001,E=3+127=130

写成二进制就是:0 10000010 001 0000 0000 0000 0000 0000

所以这个二进制(01000001000100000000000000000000)以十进制输出就会是:1091567616

而以浮点数输出就会是:9.000000。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值