C语言:数据在内存中的存储

一.整数在内存中的存储

整数的二进制表示方法有三种,即原码、反码和补码

三种表示方法均有符号位和数值位两部分,符号位都是用0表示‘正’,用1表示’负‘,而数值位最高位的一位是被当作符号位,剩余的都是数值位

正整数的原、反、补码都相同

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

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

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

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

对于整数来讲,数据存放内存中其实存放的是补码.

1.1截断

什么是截断?

截断(Truncation)是指将一个较大的数据类型转换为较小的数据类型时,丢弃高位字节或位的过程。

当将一个较大的数据类型转换为较小的数据类型时,可能会发生截断。截断意味着丢弃较大数据类型的高位字节或位,只保留较小数据类型所能表示的范围内的值。这可能导致数据的精度丢失或溢出。

例如,将一个32位整数(4个字节)转换为16位整数(2个字节)时,会丢弃高16位字节,只保留低16位字节。这就是截断。  int->short即会发生截断

1.2整型提升

什么是整型提升?

C语⾔中,整型算术运算总是⾄少以缺省(缺省就是默认的意思)整型类型的精度来进⾏的。

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

1.2.1意义

 表达式的整型运算要在CPU的相应运算器件内执⾏,CPU内整型运算器(ALU)的操作数的字节⻓度⼀ 般就是int的字节⻓度,同时也是CPU的通⽤寄存器的⻓度。

     因此,即使两个char类型的相加,在CPU执⾏时实际上也要先转换为CPU内整型操作数的标准⻓度。

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

      也就是说,假设c1和c2是char类型,那么要先将要实现c1+c2,就需要对c1和c1进行整型提升之后进行运算,那么假设我们用char类型的c3去接收c1和c2的结果,由于char类型是一个字节,所以会发生截断。截断之后,只会保留低位的字节存储在c3中!!

1.2.2如何进行整型提升

1. 有符号整数提升是按照变量的数据类型的符号位来提升的

2. ⽆符号整数提升,⾼位补0

二.大小端字节序和字节序判断

在我们了解了整数在内存中如何存储后,我们调试看一个细节:

在调试时发现,a中的0x11223344这个数字是按照字节位单位,倒着存储的————这是为什么呢?这里我们就要引入大小端这一概念

2.1什么是大小端?

其实超过一个字节的数据在内存中存储的时候,就有存储顺序的问题,按照不同的存储顺序,我们分为大端字节序存储和小端字节序存储

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

小端(存储)模式:是指数据的低位字节内容保存在内存的低地址处,而数据的高位字节内容,保存在内存的高地址处.

2.2为什么会有大小端?

这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都 对应着⼀个字节,⼀个字节为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 、x64结构是⼩端模式,⽽ KEIL C51 则为⼤端模式。很多的ARM,DSP都为⼩端模式。有些ARM处理器还可以由硬件来选择是⼤端模式还是小端模式。

2.3编写一个函数来判断当前机器的字节序(大小端的判断)

int check_sys()
{
	int a = 1;//取a的 地址,强制转换为char*再解引用即为第一个字节的数据
	return (*(char*)&a);//若返回值为1则为小端,返回值为0则为大端
}

三.经典例题讲解

3.1练习1

对于该代码结果该如何呢?接下来我们一一分析

3.2练习2

3.3练习3

四.浮点数在内存中的存储

常⻅的浮点数:3.14159、1E10(E表示底数是10)等,浮点数家族包括: float(4字节)、double(8字节)、long double(8字节) 类型。

 浮点数表⽰的范围:float.h中定义

4.1浮点数的存储形式

根据国际标准IEEE(电⽓和电⼦⼯程协会) 754,任意⼀个⼆进制浮点数V可以表⽰成下⾯的形式:

为什么是这样的形式呢?这就跟科学计数法有关系。下面通过一个例子来说明:

10进制的5.5应该怎么转换成二进制呢?

5变成2进制是101 ,0.5变成2进制是2^-1,所以可以其二进制形式可以写成101.1,用科学计数法来表示就是1.011*2^2,类比上图的表示形式我们可以发现,此时S=0,M=1.011,E=2.

既然浮点数可以写成科学计数法的形式,并且可以算出对应的S M E,所以我们实际上只需要在内存中将S M E 存储起来即可!!

IEEE 754规定:

对于32位的浮点数,最⾼的1位存储符号位S,接着的8位存储指数E,剩下的23位存储有效数字M 对于64位的浮点数,最⾼的1位存储符号位S,接着的11位存储指数E,剩下的52位存储有效数字M

4.2浮点数存的过程

  IEEE 754对有效数字M和指数E,还有⼀些特别规定。

4.2.1M

前⾯说过, 1≤M<2 ,也就是说,M可以写成 1.xxxxxx 的形式,其中xxxxxx表⽰⼩数部分。 IEEE 754规定,在计算机内部保存M时,默认这个数的第⼀位总是1,因此可以被舍去,只保存后⾯的 xxxxxx部分。⽐如保存1.01的时候,只保存01,等到读取的时候,再把第⼀位的1加上去。这样做的⽬ 的,是节省1位有效数字(这样的话精度会更高!!)。以32位浮点数为例,留给M只有23位,将第⼀位的1舍去以后,等于可以保存24位有效数字。

4.2.2 E
   

⾄于指数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。

4.3 浮点数取的过程


4.3.1 E不全为0或不全为1(常规方式)


      这时,浮点数就采⽤下⾯的规则表⽰,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第⼀位的1。 ⽐如:0.5 的⼆进制形式为0.1,由于规定正数部分必须为1,即将⼩数点右移1位,则为1.0*2^(-1),其 阶码为-1+127(中间值)=126,表⽰为01111110,⽽尾数1.0去掉整数部分为0,补⻬0到23位00000000000000000000000

则其⼆进制表示形式为:0     01111110      00000000000000000000000

4.3.2 E全为0


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

4.3.3 E全为1


    这时,如果有效数字M全为0,表⽰±⽆穷⼤(正负取决于符号位s)

五.例题讲解

对于这题,我们根据上述,一一分析

5.1整型9还原成浮点数变成了0.000000

9以整型的形式存储在内存中

00000000 00000000 00000000 00001001————9

转化为float型时,根据上述形式可以得到

第一位符号位s=0  后面8位指数E=00000000

最后23位有效数字M=000 0000 0000 0000 0000 1001

由于指数E全为0 符合上述E全为0的情况 

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

易得V是一个无限接近0的正数

5.2浮点数9.0打印结果为1091567616

首先浮点数9.0等于二进制的1001.0,换成科学计数法就是1.001*2^3

即S=0,M=1.001,E=3

首先是第一位符号位S=0,有效数字M等于001后面再加20个0,凑满23位,指数E等于3+127=130,即10000010

按照S+E+M的方式写成二进制就是

0 10000010 001 0000 0000 0000 0000 0000

转换成10进制就是就是1091567616

六.浮点数存储存在的问题 

6.1浮点数在内存中可能存在部分数无法精确保存

6.2double类型的精度比float类型高

double类型留给E和M的位数都更大,所以相对来说精度会更高

6.3两个浮点数比较大小时,直接使用==可能会存在问题

当要判断 a=3.3时,我们可以通过(a-3.3>=0.000001&&a-3.3<=0.000001)也可以使用(abs(a-3.3)<=0.000001)来实现

如果对你有帮助的话,给个三连谢谢

  • 35
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言,局部变量和函数参数等数据通常存储在栈空间。栈空间是一种自动管理的内存区域,用于存储函数的局部变量和函数调用的相关信息。 当一个函数被调用时,它的局部变量和函数参数被分配到栈空间。栈空间的分配和释放是由编译器自动完成的,遵循"后进先出"(Last-In-First-Out, LIFO)的原则。 在栈空间,局部变量的存储是通过栈指针(SP)进行的。栈指针指向当前栈顶的位置,新的局部变量被分配在栈顶,并且栈指针向下移动。当函数调用结束后,栈指针会回退到之前的位置,之前的局部变量所占用的空间就可以被重新使用。 要存储和读取栈空间数据,可以直接使用变量名来进行操作。例如,以下是一个示例函数,展示了如何存储和读取栈空间数据: ```c void exampleFunction() { int x = 10; // 在栈空间分配一个整型变量x int y = 20; // 在栈空间分配一个整型变量y int z = x + y; // 通过操作栈空间的x和y计算z的值 printf("z = %d\n", z); // 输出z的值 } ``` 在这个示例函数,变量`x`和`y`被分配到栈空间,它们可以通过变量名直接进行操作和计算。最后,通过`printf`函数将计算得到的结果输出。 需要注意的是,栈空间数据只在函数执行期间有效。当函数执行结束后,栈空间数据将被销毁,并且不能再访问这些数据。因此,栈空间主要用于存储函数调用期间所需的临时数据和局部变量。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值