深度剖析数据在计算机内存中的存储"/>
深度剖析数据在计算机内存中的存储
本章重点
1.计算机中数据类型详细介绍 2.整形在内存中的存储方式 3.大小端字节序介绍以及判断方法1. 数据类型详细介绍
C语言中我们学习的内置类型数据有以下几种。
类型 | 数据类型名称 | 占用内存空间字节 |
char | 字符数据类型 | 1 |
short | 短整型 | 2 |
int | 整形 | 4 |
long | 长整型 | 4 |
long long | 更长的整形 | 8 |
float | 单精度浮点数 | 4 |
double | 双精度浮点数 | 8 |
类型的意义:使用这个类型开辟内存空间的大小(大小决定了使用范围)。
在VS2019下观察内置类型数据的大小
#include<stdio.h>
#include<limits.h>int main()
{INT_MAX;return 0;
}
limist这个库函数可以查看内置类型数据最大值与最小值
右键点击然后转到定义
通过转到定义可以清晰的查看内置数据的最大值与最小值
整形家族分类
char | signed char | unsigned char |
shor | unsigned short | signed short [int] |
int | unsigned int | signed int |
long | unsigned long [int] | signed long [int] |
为什么char是属于整形家族的呢?因为char类型在内存中存储的是ASCII码值,是整形,因此归类为整形家族。
对整形家族具有无符号和有符号的区分 那么char是unsigned char 还是signed char呢?
这是不确定的,但在VS2019上 char=signed char short=signed short int=signed int
2. 整形在内存中的存储:原码、反码、补码
一个变量在内存中存储是需要开辟空间的,而开辟的空间大小取决于变量的数据类型。
#include<stdio.h>
#include<limits.h>int main()
{int a = -10;10000000 00000000 00000000 00001010--源码11111111 11111111 11111111 11110101--反码11111111 11111111 11111111 11110110——补码int b = 20;00000000 00000000 00000000 00010100——源码==反码==补码return 0;
}
int开辟4个字节空间大小,4个字节=32个比特位(byte)。
而计算机中表达二进制数有三种方法 源码 反码 补码。
三种表达方法均有符号位和数值位两部分组成 0表示正 1表示负 而正数的源反补都相同。
以下是8个比特位的内存空间所放二进制数值的所有可能以及转换。
有符号:signed 转换图
无符号:unsigned 转换图
则负数的源 反 补表达方式不同
源码:直接将数值按照正负数的形式翻译成二进制就可以得到原码。
反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码+1就得到补码。
补码到源码可以有两种方式获取
先取反+1 or 先-1取反
3. 大小端字节序介绍及判断
对于整形来说: 数据存放内存中其实存放的是补码。 为什么呢? 在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统 一处理; 同时,加法和减法也可以统一处理(CPU 只有加法器 )此外,补码与原码相互转换,其运算过程 是相同的,不需要额外的硬件电路。 通过调试查看内存存储空间#include<stdio.h>
#include<limits.h>int main()
{int a = -10;10000000 00000000 00000000 00001010--源码11111111 11111111 11111111 11110101--反码11111111 11111111 11111111 11110110——补码ff ff ff f6int b = 20;00000000 00000000 00000000 00010100——源码==反码==补码00 00 00 14return 0;
}
a的地址 b的地址 a的补码是ff ff ff f6 b的补码是00 00 00 14 内存中存储的值是十六进制 通过调试发现内存存储的顺序是反的?这又是为什么呢? 这时候就要介绍大小端了。 大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址 中; 小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地 址中。 FF FF FF F6 万 千 百 个 位 00 00 00 14 万 千 百 个 位 为什么会有大小端? 为什么会有大小端模式之分呢? 这因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为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处理器还可以由硬件来选择是大端模式还是小端模式。
怎么判断机器字节序是大端还是小端呢?
#include<stdio.h>
int check_key()
{int n = 1;00000000 00000000 00000000 0000000100 00 00 01 小端字节 01 00 00 00 大端字节 00 00 00 01char b = *(char*)&n;return b;
}
int main()
{int ret = check_key();if (ret == 1){printf("小端\n");}else{printf("大端\n");}return 0;
}
我们先强制类型转换成char类型再取n的地址,再解引用访问地址,如果是1就是小端 否则大端。
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 255 为什么呢?
#include <stdio.h>
int main()
{char a = -1;10000000 00000000 00000000 00000001——源码11111111 11111111 11111111 11111110——反码11111111 11111111 11111111 11111111——补码 ——截断11111111 -a整形提升11111111 11111111 11111111 11111111 ——补码10000000 00000000 00000000 00000000 ——取反10000000 00000000 00000000 00000001 ——源码signed char b = -1;10000000 00000000 00000000 00000001——源码11111111 11111111 11111111 11111110——反码11111111 11111111 11111111 11111111——补码 ——截断整形提升11111111 11111111 11111111 11111111 ——补码10000000 00000000 00000000 00000000 ——取反10000000 00000000 00000000 00000001 ——源码unsigned char c = -1;10000000 00000000 00000000 00000001——源码11111111 11111111 11111111 11111110——反码11111111 11111111 11111111 11111111——补码 ——截断整形提升1111111100000000 00000000 00000000 11111111 ——正数 源反补相同printf("a=%d,b=%d,c=%d", a, b, c);%d打印的是十进制数字 输出的是源码的值-1 -1 255return 0;
}
为什么会整形提升和截断呢? C 的整型算术运算总是至少以缺省整型类型的精度来进行的。 为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型 这种转换称为整型 提升 。 整型提升的意义:表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU (general-purpose CPU) 是难以直接实现两个8比特字节直接相加运算 (虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。 负数char的整形提升:一般高位补充符号位 无符号char的整形提升:一般高位补充0
char a = -1;
1000000 0000000 0000000 00000001 ——源码
1111111 1111111 1111111 11111110 ——反码
1111111 1111111 1111111 11111111 ——补码 因为char只有一个字节大小进行截断11111111 -a 截断
整形提升
1111111 1111111 1111111 11111111unsigned char a = -1;
1000000 0000000 0000000 00000001 ——源码
1111111 1111111 1111111 11111110 ——反码
1111111 1111111 1111111 11111111 ——补码 因为char只有一个字节大小进行截断11111111 -a 截断
整形提升
00000000 00000000 00000000 11111111 为正数 源反码相同
2.第二道习题
输出什么?
#include <stdio.h>
int main()
{char a = -128;printf("%u\n",a);return 0;
}
答案输出4,294,967,168 为什么呢?
因为是无符号数 没有符号位 全是数值位
不相信的同学 可以win+R 输出calc 弹出计算器切换到程序员
把整形提升后的二进制序列输入进去 就可以看到结果了。
#include <stdio.h>
int main()
{char a = -128;1000000 0000000 0000000 10000000 ——源码1111111 1111111 1111111 01111111 ——反码1111111 1111111 1111111 10000000 ——补码 截断10000000 -a整形提升11111111 11111111 11111111 10000000printf("%u\n", a);4,294,967,168return 0;
}
3..第三道习题
这个输出什么?
#include <stdio.h>
int main()
{char a = 128;printf("%u\n",a);return 0;
}
答案还是4,294,967,168
#include <stdio.h>
int main()
{char a = 128;00000000 00000000 00000000 10000000——正数 源反码相同截断10000000整形提升11111111 11111111 11111111 10000000printf("%u\n", a);return 0;
}
原理与上题相同
4.第四道习题
输出什么?
#include<stdio.h>
int main()
{
int i= -20;
unsigned int j = 10;
printf("%d\n", i+j); }
答案是-10
#include <stdio.h>
int main()
{int i = -20;1000000 00000000 00000000 00010100 ——源码11111111 11111111 11111111 11101011 ——反码11111111 11111111 11111111 11101100 ——补码unsigned int j = 10;00000000 00000000 00000000 00001010 ——正数 源反码相同相加补码11111111 11111111 11111111 11101100 -i00000000 00000000 00000000 00001010 -j11111111 11111111 11111111 11110110 补码相加结果10000000 00000000 00000000 0000100110000000 00000000 00000000 00001010 ——源码 -10printf("%d\n", i + j);-10return 0;}
5.第五道习题
输出什么?#include<stdio.h>int main()
{unsigned int i;for (i = 9; i >= 0; i--){printf("%u\n", i);}return 0;
}
答案是无限循环!为什么呢?因为从上面的转换图可以看出,无符号数是没有符号位的,当i--到0的时候,再-1 会变成全11111111 11111111 11111111 11111111 因为无符号数没有符号位全是数值位,会一直减下去 变成0 又从0开始循环 所以是一个死循环。
我们可以通过Sleep函数观察下此代码
#include<stdio.h>
#include<Windows.h>
int main()
{unsigned int i;for (i = 9; i >= 0; i--){Sleep(1000);//毫秒printf("%u\n", i);}return 0;
}
6.第六道习题
输出什么?
#include<stdio.h>
int main()
{char a[1000];int i;for (i = 0; i < 1000; i++){a[i] = -1 - i;}printf("%d", strlen(a));return 0;
}
答案是255。为什么呢?
从数学的角度来讲 代码理论应该是这样的 如下图
a[i]会从-1一直减到-1000 但实际上不是这样的。
char的范围是-128~127
当从-1减到-128时 再减去1 会变成127 126 ....到0
strlen是求字符串长度的,统计的是\0之前的出现的字符个数,而\0等同于0
-1到-128 是128个数字 127到0 一共是128个数字 但0==\0 所以是128+127=255
因此strlen只统计0之前出现的数字字符个数 因此输出结果是255。
更多推荐
深度剖析数据在计算机内存中的存储
发布评论