此篇博客带大家具体了解了C语言整形和浮点型在内存中的存储情况,通过这篇博客我们将会对内存有进一步的了解。

文章目录

  • 一、回顾数据类型
    • 1.1类型的基本归类
  • 二、整形在内存中的存储
    • 2.1原码、反码和补码
    • 2.2大小端
      • 2.2.1为什么要有大小端?:
      • 2.2.2用代码判断机器的字节序(大小端):
  • 三、数据类型存储的一些练习题
  • 四、浮点数在内存中的储存

一、回顾数据类型

我们之前已经对C语言中的内置数据类型有了了解:

char      //字符数据类型,比如a、b、c、!等字符
short      //短整型                用来描述范围更小的整数
int         //整形                用来描述整数
long        //长整型               用来描述范围更大的整数
long long   //更长的整形            用来描述范围更大的整数
float       //单精度浮点数        用来描述小数
double      //双精度浮点数       比float精度更高

所谓内置类型,就是C语言本身自带的类型,除此之外还有自定义类型比如结构体等。

这些内置类型的意义:

  1. 使用这个类型开辟内存空间的大小(内存空间的大小决定了使用范围)。
  2. 决定了如何看待内存空间的视角(比如整形5和浮点型5.0在内存中的存储是不一样的)。



可以看到,整形和浮点型的存储方式是不同的。

1.1类型的基本归类

  • 整形
char//字符在内存中的存储是ASCII码值,ASCII码值也是整数,所以将char归类到整形unsigned charsigned char
shortunsigned short [int]signed short [int]
intunsigned intsigned int
longunsigned long [int]signed long [int]

usigned意为无符号型,signed意为有符号型,无符号型的最高位并不是符号位,因此无符号型只能表示无符号数(可以理解为正整数),表示的正整数的范围也比有符号型大。

  • 浮点型
float
double
  • 构造类型
> 数组类型  //int a[10]  int b[5]  char c[5] 这三个数组都是不同的数组类型
> 结构体类型 struct
> 枚举类型 enum
> 联合类型 union
  • 指针类型
int* pi;
char* pc;
float* pf;
void* pv;
  • 空类型

void 表示空类型(无类型)
通常应用于函数的返回类型、函数的参数(函数的参数如果是void,则不能传参)、指针类型(无具体类型的指针)


二、整形在内存中的存储

一个变量创建时要在内存中开辟空间,开辟的内存空间大小决定于该变量的数据类型。
如果我们创建两个整形变量:

int main()
{int a=16;int b=-16;return 0;
}

他们在内存中是这样存储的:

要解释这俩变量存储的不同,首先要回顾一下原码、反码、补码:

2.1原码、反码和补码

计算机中的有符号整形有三种表示方法,即原码、反码和补码。(无符号数也有原反补码三种表示形式,只不过他们的原反补码都相同)
三种表示方法均有符号位数值位两部分,一般用最高位表示符号位,符号位都是用0表示“正”,用1表示“负”,而数值位三种表示方法各不相同。

原码
原码就是整数的二进制表示形式。

反码
反码就是原码的符号位不变,其他位按位取反(0变成1,1变成0)

反码
补码则是在反码的基础上加1

因此上面的变量a和b的原反补码如下:

int a=16;
00000000000000000000000000010000
原码、反码、补码相同,都是这样int a=-16;
原码:10000000000000000000000000010000
最左边的1表示负数的意思。
反码:11111111111111111111111111101111
补码:11111111111111111111111111110000

而数据是以16进制补码的形式存储的(为什么要以补码的形式存在,之前在操作符和表达式一章中说明过),所以他们的存储方式应该是00 00 00 10和ff ff ff f0,但是编译器是倒着存放的,这就涉及到大小端的概念了:

2.2大小端

计算机存储方式有两种:

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

比如0x12345678这个数据:
大端存储:

小端存储:

2.2.1为什么要有大小端?:

大小端又叫大端字节序存储模式和小端字节序存储模式,所以大小端的不同其实是变量字节序的存储方式不同。
在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如果将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
例如一个 16bit 的 short 型 x ,在内存中的地址为 0x0010 , x 的值为 0x1122 ,那么 0x11 为高字节, 0x22为低字节。对于大端模式,就将 0x11 放在低地址中,即 0x0010 中, 0x22 放在高地址中,即 0x0011 中。小端模式,刚好相反。我们常用的 X86 结构是小端模式,而 KEIL C51 则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。

这两种方式各有优劣:
小端模式:强制转换数据不需要调整字节内容,1、2、4字节的存储方式一样,比如将int型转化为char型。
大端模式:符号位的判定固定为第一个字节,容易判断正负。
因此这两种方式没有优劣之分,只是不同的地方用不同的存储方式罢了,就像计量单位有英制公制,汽车有左舵右舵,一开始没有统一标准,就只好双轨并用了。最开始应该是硬件实现上各有优势,所以沿用下来了。

2.2.2用代码判断机器的字节序(大小端):

对于一个变量a=1,如果是大端,则它的低地址存储的是00,如果是小端,则低地址存储的是01,因此我们只需要将低地址的值拿到然后判断就行了,可以将a的地址强转为char*的指针,然后解引用拿到低地址(char *指针解引用只能访问一个字节)的内容:

#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;
}

三、数据类型存储的一些练习题

无论任何位运算符,目标都是要计算机进行计算的,而计算机中只有CPU具有运算能力(先这样简单理解),但计算的数据, 都在内存中。故,计算之前(无论任何运算),都必须将数据从内存拿到CPU中,拿到CPU哪里呢?毫无疑问,在CPU 寄存器 中。而寄存器本身,随着计算机位数的不同,寄存器的位数也不同。一般,在32位下,寄存器的位数是32位。 可是,你的char类型数据,只有8比特位。读到寄存器中,只能填补低8位,那么高24位呢? 就需要进行“整形提升”。

  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;
}

char a=-1;
首先-1是一个整数,它的大小是4个字节也就是32位,1000000000000000000000000000001
它的补码为:11111111111111111111111111111111
但是a是char类型,这个类型的变量a只能存放1个字节也就是8位:11111111同理
signed char b=-1;
signed char 类型的b只能存放8位:11111111unsigned char c=-1;
unsigned char类型的c只能存放8位:11111111在打印的时候要发生整形提升,a和b补符号位,整形提升后的补码为:11111111111111111111111111111111
原码打印后的结果为-1c是无符号型,高位不是符号位,因此整形提升高位补0,整形提升后的补码为:
0000000000000000000000011111111,由于高位是0,所以是个正数,原反补码相同,原码打印后的结果为255
  1. 下列程序输出内容:
#include <stdio.h>
int main()
{char a = -128;printf("%u\n",a);//%u打印十进制的无符号数return 0;
}

-128是一个整数,它的大小是4个字节也就是32位,1000000000000000000000010000000
它的补码为:11111111111111111111111110000000
char a中存放的是10000000
在打印时a发生整形提升:11111111111111111111111110000000
以无符号数的形式打印,因此编译器认为a整形提升后的补码和原码是相同的,因此直接以十进制的形式打印
11111111111111111111111110000000,最终的结果就是4294967168
#include <stdio.h>
int main()
{char a = 128;printf("%u\n", a);return 0;
}

128的原反补码都是0000000000000000000000010000000
char a中存放10000000
因此这个程序的结果和char a=-128的结果相同

不难发现,由于char型变量只有8个字节,最高位还表示符号位,因此char类型的数值位只有7个比特位,所以char类型能表示的范围是-128~127:

如果将127+1,就会直接变成-128,而-1+1则会变成0,因此就会形成下面的闭环:

所以上面第三题就不难理解了,127+1以后会得到-128

#include <stdio.h>
int main()
{int i = -20;unsigned int j = 10;printf("%d\n", i + j);return 0;
}

将i和j的补码相加,相加后再将结果转化为原码即是最终的结果

#include <stdio.h>
#include<Windows.h>
int main()
{unsigned int i;for (i = 9; i >= 0; i--) {printf("%u\n", i);Sleep(1000);}    return 0;
}

由于i是个无符号数,因此i>=0永远成立,所以程序会死循环。

#include<stdio.h>
#include<string.h>
int main()
{char a[1000];int i;for (i = 0; i < 1000; i++){a[i] = -1 - i;}printf("%d", strlen(a));return 0;
}

a是一个字符数组,里面元素的范围是-128~127,因此for循环以后,a中的内容为:

-1,-2,-3,-4,........-127,-128,127,126,......2,1,0,-1,-2......

由于这是个字符数组,0的ASCII码值'\0'相同,所以strlen会计算0前面的字符长度,也就是-1~1的长度,因此结果为255

#include <stdio.h>
unsigned char i = 0;
int main()
{for (i = 0; i <= 255; i++){printf("hello world\n");}return 0;
}

结果为死循环,因为unsigned char i的范围是0~255,255+1=0


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

根据国际标准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位(4个字节)的浮点数,最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M。


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

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。

现在,我们就知道了浮点数如何在内存中存储的了,拿浮点数5.5举例:

5.5写成二进制形式是101.1(小数的1代表2^(-1)也就是0.5)
写成(-1)^s * M * 2^E的形式:
(-1)^0*1.011*2^2
因此S=0  M=1.011  E=2
所以S内存中存0  E在内存中存2+127=129  M存011
他们写成二进制以后:
0 10000001 01100000000000000000000
合并以后得到这样的32位的二进制数:
01000000101100000000000000000000
将这串二进制数转化为十六进制:
0x40b00000
而内存为小端存储,所以储存为
0000b040


当E从内存中取出时会有三种情况:

  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

  1. E为全0
    如果E为全0,说明E原来的值为-127或者-1023,这个数是超级小的,float和double的精度无法表示这个数

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

  1. E为全1
    如果E为全1,则E原来的值为128,这个数是超级大的,float和double的精度无法表示这个数

这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);

所以,看下面的程序:

#include<stdio.h>
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转化为float*指针再解引用以后,就是按照float的内存形式打印了:将n的二进制形式00000000000000000000000000001001拆分,得到第一位符号位s=0,后面8位的指数 E=00000000 ,最后23位的有效数字M=000 0000 0000 0000 00001001。由于指数E全为0,所以符合上一节的第二种情况。因此,浮点数V就写成: V=(-1)^0 ×0.00000000000000000001001×2(-126)=1.001×2(-146) 显然,V是一个很小的接近于0的正数,所以用十进制小数表示就是0.000000。

浮点数9.0等于二进制的1001.0,即1.001×2^3。那么,第一位的符号位s=0,有效数字M等于001后面再加20个0,凑满23位,指数E等于3+127=130,即10000010。 所以,写成二进制形式01000001000100000000000000000000,这个32位的二进制数,还原成十进制,正是1091567616。


09数据在内存中的存储相关推荐

  1. C语言基础09——数据在内存中的存储。整型的存储、大小端讲解、浮点数的存储、杨辉三角、找凶手、猜名次

    目录 数据类型 基本内置类型 类型的基本分类 整型在内存中的存储 计算机中整数的三种表示方法:原码.反码.补码 大小端 练习 浮点型在内存中的存储 为什么以下程序输出结果与想象不同? 浮点数存储规则 ...

  2. c语言字母是怎么存储,C语言之数据在内存中的存储

    C语言之数据在内存中的存储 在我们学习此之前,我们先来回忆一下C语言中都有哪些数据类型呢? 首先我们来看看C语言中的基本的内置类型: char //字符数据类型 short //短整型 int //整 ...

  3. C语言——深度剖析数据在内存中的存储

    大家好!我是保护小周ღ,本期为大家带来的是深度剖析数据在内存中的存储,不知道,大家学了这么久C语言,有没有想过一个问题,我们在程序设计中的数据是怎么在计算机中存储的?我们都知道 一个整型数据 int ...

  4. 【C语言】浮点型数据在内存中的存储方式

    目录 一. 前言 二. 问题的引出 三. 两类浮点型数据(float.double)在内存中的存储方式 3.1 两类浮点型数据的存储模型 3.1.1 浮点型数据数值读取的通用模型 3.1.2 floa ...

  5. JavaScript中数据在内存中的存储方式

    JavaScript中数据在内存中的存储方式 1.js数据类型分类 简单数据类型:Number.String.Boolean.Undefined.Null 复杂数据类型:Object.Array.Fu ...

  6. C语言—深度剖析数据在内存中的存储

    深度剖析数据在内存中的存储 数据类型介绍 类型的基本归类 整形在内存中的存储 大小端介绍 整形在内存中的存储的相关练习 浮点型在内存中的存储 浮点型在内存中的存储相关介绍 数据类型介绍 内置类型(C语 ...

  7. 【C语言】全面解析数据在内存中的存储

    文章目录 前言 类型的基本分类 整型 浮点数 自定义类型 整型在内存中的存储 原码.反码.补码 大端和小端 如何判断编译器是大端还是小端 浮点数在内存中的存储 总结 前言 C语言中有char.shor ...

  8. 带你深度剖析《数据在内存中的存储》——C语言

    文章目录 一.数据类型介绍 二.整型在内存中的存储方式 2.1 原码.反码.补码的讲解 2.2 大小端介绍 2.2.1 大小端的概念 2.2.2 为什么要区分大小端存储呢? 2.2.3 大小端判断练习 ...

  9. C语言中数据在内存中的存储

    要想了解数据在内存中的存储的话,首先应该了解数据的类型. 下面介绍C语言中数据类型: 1.C语言中的基本内置类型: char //字符数据类型 大小为1个字节 short //短整型 大小为2个字节 ...

最新文章

  1. python入门基础代码图-Python入门基础学习一
  2. 使用API获得SAP CRM Sales Area数据
  3. 学计算机的你伤不起啊(转)
  4. php的验证码要gd库,PHP通过GD库实现验证码功能
  5. python切片输出_Python语言之详解切片
  6. 洛谷 P2261 [CQOI2007]余数求和 解题报告
  7. Unity3d发布webplayer 部署到IIS
  8. java hdfs ha_hadoop2.x hdfs完全分布式 HA 搭建
  9. 磊科nw336+linux驱动程序,磊科NW336无线网卡驱动程序
  10. 软件测试作业随笔之二:Homework 2
  11. 3dmax linux版本,如何安装Linux版FLOW-3D及注意事项
  12. a0图框标题栏尺寸_机械制图标准中规定的标题栏尺寸
  13. 带你Dart带你Diao之类(二)
  14. ffmpeg将amr格式转成mp3格式
  15. 在手机与计算机之间进行文件传输的方式,电脑与手机快速传输文件的方法
  16. ​Podman Desktop: 一款超高颜值和功能强大的 Podman 桌面管理工具
  17. 常用软件性能测试工具
  18. 英语48个常见语法点(未完待续)
  19. Gartner发布商业模式创新框架
  20. [javaEE]怎样获得已安装的Tomcat的版本呢?

热门文章

  1. 凤凰雪茶成为凤凰县推动村级集体经济发展的新引擎
  2. kindle 邮箱收不到你推送的书
  3. 网络原理 | 传输层重点协议之TCP协议(TCP连接的三次握手与四次挥手、TCP的安全机制与效率机制)
  4. 江苏省2022年普通高校专转本选拔考试——计算机专业大类专业综合基础理论试卷
  5. 联想机系统重装全教程
  6. 中国移动5G套餐预约用户已超100万
  7. 伪装计算机主机,位置伪装大师电脑版
  8. 【架构基础】简单设计原则
  9. nfc使用实验10-11
  10. 初入云计算行业,可以考取哪些云计算证书?