【C语言】整型数据在内存中的存储原理 (附有例题和解析)

您所在的位置:网站首页 内存单位是字节还是位 【C语言】整型数据在内存中的存储原理 (附有例题和解析)

【C语言】整型数据在内存中的存储原理 (附有例题和解析)

2023-07-12 21:15| 来源: 网络整理| 查看: 265

目录 前言1. 数据类型1.1. 常见的数据类型1.1.1. 基本数据类型的分类1.1.2. 基本数据类型所占存储空间的大小 1.2. 为什么要有不同的数据类型? 2. 整型数据在内存中的存储2.1. 计算机的数据存储2.1.1. 常见的进制2.1.2. 进制的换算方法 2.2. 原码,反码,补码2.2.1. 原反补码存在的意义 2.3. 整数类型存储的取值范围 3. 大小端字节序存储3.1. 什么是大小端字节序?3.2. 测试当前机器是大端还是小端? 4. 例题和解析

前言

代码语言:C语言 开发环境:Visual Studio 2022 通过这篇文章,你将了解到:

整型家族成员;整型在内存中的存储方式;大小端字节序存储。 1. 数据类型 1.1. 常见的数据类型

Alt

1.1.1. 基本数据类型的分类

以下是基本数据类型的归类: Alt 对上图的解释:

signed是有符号,unsigned是无符号,平常写的int类型就是signed int类型,以此类推。signed通常可以省略不写。[int]指的是该int可以省略不写。比如unsigned short int类型,可以省略int,等价于unsigned short类型。char类型是字符类型,字符存储的时候存的是ASCII码值,ASCII码值是整数,所以char类型归类到整型家族。整型家族的各个成员只是存储数据的取值范围不同,但都是整数。既然是整数就可以表示正整数,0和负整数,有unsigned修饰的数据类型就不能表示负整数。 1.1.2. 基本数据类型所占存储空间的大小

Alt 对上图对的解释:

1.在不同的编译器下长整型的存储空间是不同的,C语言只规定:long类型所占空间大小 >= int类型所占空间大小( sizeof(long) >= sizeof(int) )。 2. C语言还明确规定long long类型是8个字节,float类型是4个字节,double类型是8个字节。

1.2. 为什么要有不同的数据类型? 不同类型占不同大小的空间,根据具体的数据合理分配空间。不同的数据类型决定了开辟内存空间的大小。内存空间的大小不同,存储数据的取值范围也就不同。数据类型不同,看待内存空间的视角就不同。 2. 整型数据在内存中的存储

创建一个变量,就要给这个变量开辟一个空间,这个空间的大小由数据类型决定。创建好一个变量后,下一步就是存储数据。10怎么存储?-10又怎么存储?下面就是讲整型家族的数据是如何存储的。

2.1. 计算机的数据存储

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

2.1.1. 常见的进制

最常见的十进制数字:由0,1,2,3,4,5,6,7,8,9组成。逢10进1

二进制数字:由0,1组成。逢2进1

16进制数字:由0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f 组成。逢16进1 16进制数字如果用10,11,12,13,14,15表示会与前面的0到5的数字产生歧义,所以从10到15,分别用a,b,c,d,e,f表示。

八进制数字:由0,1,2,3,4,5,6,7组成。逢8进1

为了防止把16进制的1,二进制的1,八进制的1和十进制的1混淆,一个16进制数字会在前面加一个前缀0x。一个八进制数字会在前面加一个前缀0。 C语言中没有直接表示二进制的前缀。

例子: 0x1234中的0x除了表示1234是16进制数字没有任何其他意思。 01234中的0是八进制的标志。 1234没有任何前缀就是十进制的1234。1234的二进制是010011010010。具体怎么换算看下一节。

2.1.2. 进制的换算方法

通过两个例子举一反三

任意进制转十进制 Alt Alt

十进制转任意进制(短除法) Alt Alt

使用进制换算计算器计算,这里用Windows自带的计算器介绍。 打开Windows计算器,点击左上角的打开导航,选中计算器的程序员。 Alt HEX是16进制 DEC是十进制 OCT是八进制 BIN是二进制 点击DEC输入418,就可以看到各个进制的表示形式 在这里插入图片描述

2.2. 原码,反码,补码

原码:十进制数据的二进制表示形式。为了分清正负数,把最高位(最左边)规定为符号位(0为正,1为负),其他位为数值位。

以一个字节大小(8个比特位)为例:00010101是21的原码,10010101是-21的原码 Alt 现在想让 21 和 -21 的原码进行相加,是否会等于0呢? Alt 0的二进制应该是00000000,可是上面计算的结果显然不是。试试 21 加 1: Alt 正数相加没问题,再看看负数相加,比如 -21和-1相加:

Alt -21+(-1)本应该是-22,可结果得到的是22。由这3个例子可以知道:

原码是负数时不能计算出正确的结果。

而反码的存在就是为了能解决负数计算的问题。

反码:正数的反码是原码本身,负数的反码是符号位不变,其余位取反。

举个例子(以一个字节的大小为例):

Alt 因为正数的反码和原码一样,也能进行正数的计算。接着我们试试用反码进行负数的计算: Alt 貌似可以计算,但还有特殊情况: Alt 为什么小了1?因为0多算了一次。0有+0和-0,+0的反码是原码本身:00000000,-0的原码是10000000,所以-0的反码是11111111。因为0算了两次,所以少了个1。也体现出反码计算的缺陷:负数计算的结果跨越0,会有1的偏差。 而补码的出现就是为了解决跨0计算的问题。

补码:正数的原码,反码和补码都是相同的,负数的补码就是反码加1。 一张图明白负数的原码,反码和补码: 在这里插入图片描述 此时不管是+0还是-0都是00000000,就不影响计算了。

2.2.1. 原反补码存在的意义

实际上,整型数据在内存中都是以补码的形式存储的。当我们输入一个整数,计算机先把这个整数转换为二进制的形式,也就是原码,然后再转换为补码的形式进行运算。 为什么还要转换为补码再进行计算,不麻烦吗? 用补码可以直接计算正负整数,且CPU只有加法器,也就是只能进行加减法运算(减法可以加一个负数实现),并且原码转补码和补码转原码可以用取反加1实现转换,不需要再使用额外的机器进行换算。效率反而更高。 Alt 最后再回过头来看看10和-10的存储方式: Alt 我们可以在开发环境下看到10和-10在内存中存储的位置: Alt 本质上内存存放的是二进制,但为了方便显示,该环境(vs2022)显示的是16进制 (注意内存中的数据要倒着读,原因在大小端字节序中解释。),变量num和num2的空间大小都是4个字节(也就是32个比特位),因为1个16进制数字需要用4个二进制数字表示,8个16进制数字就要32个二进制数字表示,所以16进制的00 00 00 0a就是二进制的00000000 00000000 00000000 00001010,也就是10。

画图解释: Alt -10同理: Alt Alt

2.3. 整数类型存储的取值范围

列举部分整型家族的数据类型: char:-128 到 127 (有符号) unsigned char: 0 到 255 (无符号)

short:-32,768 到 32,767 (有符号) unsigned short: 0到65535 (无符号)

int:-2,147,483,648 到 2,147,483,647 (有符号) unsigned int: 0到4294967295 (无符号)

long:-9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 (有符号) unsigned long: 0到18446744073709551615 (无符号)

怎么得出的取值范围?以char类型为例: char类型的大小是1个字节(8个比特位) Alt +1轮回: Alt

3. 大小端字节序存储

内存里以1个字节为单位,如果需要存储一个大小为多个字节的整型数据时,就需要多个内存单元存储这个数据,那么要怎么存?顺着存还是倒着存?这是个问题。实际上,不同的机器存的顺序不同,这里需要了解大小端的概念。

3.1. 什么是大小端字节序?

1. 字节序:以字节为单位讨论存储顺序。char类型不讨论顺序,因为char类型的数据只占一个字节。 2. 大端字节序存储:把一个数据的低位字节的内容,存放在高地址处,把这个数据的高位字节的内容,存放在低地址处。 3。 小端字节序存储:把一个数据的低位字节的内容,存放在低地址处,把这个数据的高位字节的内容,存放在高地址处。

举例,在int类型的变量中存储一个数 0x11223344: Alt

3.2. 测试当前机器是大端还是小端?

创建一个整型变量a,把1赋值给a,内存会为变量a开辟连续的4个字节的空间并且存储1。如果该机器是小端字节序存储,那么读取变量a的最低位的字节时读取到的应该是1,否则该机器是大端字节序存储。 Alt 为什么(int* )类型的地址a要强制转换为(char*)类型? 对(int* )类型的地址进行解引用,对该地址访问对象的大小是4个字节; 对(char* )类型的地址进行解引用,对该地址访问对象的大小是1个字节; 而我们只需要看变量a地址指向的第一个的字节是否是1即可,不访问后面3个字节的数据,所以把地址a的(int* )类型强制转换为(char*)类型。 Alt 代码实现如下:

#include //小端返回1,大端返回0 int check_system() { int a = 1; return *(char*)&a; } int main() { int ret = check_system(); if(ret ==1) printf("小端"); else printf("大端"); return 0; } 4. 例题和解析

例1:

#include 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解析: Alt

例2:

#include int main() { char a = -128; char b = 128; printf("%u,%u", a,b); return 0; }

例2解析: Alt Alt

例3:

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

例3解析: Alt 例4:

#include int main() { unsigned int i; for (i = 9; i >= 0; i--) { printf("%u\n", i); } return 0; }

例4解析: unsigned int类型的取值范围:0到4294967295,是不可能出现负数的,所以i>=0是恒成立的,出现死循环。 0减1会变成32个1组成的二进制序列,直接转换为二进制序列就是4294967295,然后一只减1,最后减到0又回到4294967295…

例5:

#include #include int main() { char a[1000]; int i; for (i = 0; i unsigned char i=0; for (i = 0; i


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3