信息的存储与值的计算
前言
我们很难想象出来,计算机只能识别0和1这样再简单不过的数字,却给人们带来了巨大的变化.对于无法与人脑相比的计算机来所,简单的1和0就是适合他们的数字.不过呢,一个1或者1个0往往代表不了什么意义,,他们必须被赋予上下文,才能有具体的含义.比如,如果我们知道1和0代表的布尔类型的值,那么我们就知道1是true,0是false.
对于二进制所表示的数字来说,主要有三种,即无符号,补码以及浮点数.
由于计算机对于固定类型的二进制数字往往都是有位数限制的,比如int类型使用四个字节(32位二进制)来表示,因此在计算的时候,会发生溢出的情况,最简单的是我们使用无符号整数0xFFFFFFFF与无符号的0xFFFFFFFF相乘会溢出.
在产生溢出的时候,得出的结果往往令人大跌眼镜.比如两个正数相乘可能得到负值,两个正数相加也可能得到负值.而对于不同的计算机来所,由于数值范围可能有所不同,因此掌握信息相关的内容对于写出扩平台的程序来所也是很有帮助的.
信息的存储
大多是计算机使用8位的块,或者说字节,来作为最小的可寻址的存储器单位,问不是在存储器中访问单独的位.换句话说,我们在访问存储器的内容时,最小的访问单位一般是字节.
在我们变成的时候,往往会把虚拟存储器抽象为一个字节数组,而每一个数组内的元素或者说字节都有唯一的地址,这些地址的集合就被称为虚拟地址空间.虚拟地址空间是为了给机器级的程序一个概念上的映像,实际上为了提供这个映像,内部的实现是非常复杂的.
十六进制表示法
对于机器来说,可能比较喜欢0和1,但是对于人类来说,1和0就不够看了.因为通产情况下,为了便于阅读,我们会使用十六进制去表示二进制.1位十六进制的数字可以表示4位二进制数字,因此一个字节就可以表示为0x00--0xFF.
有关十六进制,二进制,十进制之间的转换你得弄明白,要想明白加减乘除一样的记住.
每台计算机都有一个字长,啥是字长?你需要先明白字的概念,由于虚拟地址空间的地址就是使用一个字来表示的,因此OS中的字长就决定了虚拟地址空间的大小.字长就是用于指明整数和指针数据的标准大小.
比如在32位的OS下,最大的内存就是232 = 4 * 210 * 210 * 210 B = 4GB,
在64位的计算机上,最大的内存是234GB.
这就代表了如果你的电脑是32位的OS,则表示最大的可用内存就是4G,你就算有个1000G的内存条也是白搭,只能使用4G.相对而言呢,说是64位的OS能支持到2的32次方G,但是根据实际来说,你电脑的各个硬件能支持到16G就不错了,我的电脑支持8G就到头了...
数据大小
由于计算机位数的不同,会造成在数据类型的存储上,采用的位数略有不同,下图表示了在32位和64位的机器下,C语言当中的数字数据类型需要的位数.
我相信你只要不瞎,应该能看出在32位和64位的OS下所需的位数不同吧,这是C语言的数据类型.对于长整型以及字符指针来说,在32位和64位系统下的字节数时不同.特别的,对于指针类型来说,所有指针类型在32位下都是4位,而在64位下都是8位,这是由虚拟地址空间的地址位数或者说字长所决定的.
寻址和字节顺序
对于跨越多个字节的程序对象(程序对象指令,数据或者控制信息等,是程序当中对象的通常)来说,我们需要指定两个规则,才能唯一确定一个程序对象的值.
比如对于int类型的值0xFF来说,如果我们要根据虚拟内存地址去获取这个整数值,那么首先我们需要知道他的气势虚拟内存地址是多少.另外,我们还需要知道,对于表示int类型的四个字节来说,这四个字节的排列顺序.
对于第一个问题,由于大部分计算机都采用连续的内存地址去存储一个程序对象,因此我们称内存地址中最小的那个就是该程序对象的起始地址,也是该程序对象的地址.
对于第二个对象,一把有两种方式,即大端法和小端法.对于一个整数0x00000FF,我们假设他的起始地址为0x1,那么对于使用大端法规则的系统来说,0x1-0x4的虚拟内存所存储的值依次为0x00,0x00,0x00,0xFF,相反对于采用小端发规则的系统来说,0x1-0x4的虚拟内存所存储的值依次为0
xFF,0x00,0x00,0x00.
强制类型转换
对于一个特定的数据类型来说,计算机再解释这类数据的值的时候,是根据起始位置以及数据类型的位数来确定的.比如对于无符号的int类型的数据来说,倘若我们知道他的起始位置为0x1,而当前的OS采取的是大端法规则,假设0x1-0x4的内存地址中存储的字节依次为0xFF,0xFF,0xFF,0xFF由此计算机将会帮助我们计算出这个无符号int类型的值为65535,也就是无符号int类型的最大值.
这其中计算机是根据0x1-0x4这四个字节上的值,以及无符号int类型的解释方式,最终得到这个无符号int类型的值.
由此可见,计算机再解释一个数据类型的值时主要有四个因素:位排列规则(大端或者小端),起始位置,数据类型的字节数,数据类型的解释方式.
对于特定的系统来说,前两种因素都是特定的,而对于后两种因素的改变,则可以改变一个数据类型的值的最终计算结果,这就是强制类型转换.对于大部分的高级程序设计语言来说,都提供了强制类型转换.
比如C语言,我们可以将一个无符号int类型的值强制转换为其他类型,在转换之后,对于上面四个因素之中,改变的是最后两个.为此我们写一个程序来看看:
#include <stdio.h>
int main(){
unsigned int x = 0xFFFFFF61;
int *p = &x;
char *cp = (char *)p;
printf("%c\n",*cp);
}
这是一个简单的强制类型转换示例.可以看到我们将一个无符号的int类型的值,先赋给了一个int类型的指针,又强制转换成了char类型的指针,最终我们输出这个char类型指针所代表的字符,结果是输出一个a.
输出a的原因就是由上面那四个因素造成的(位排列顺序,起始位置,数据类型的字节数,数据类型的解释方式.)
1.cp指针的值与x变量的其实内存地址相等.(起始位置)
2.我的OS是小端表示法,也就是说假设x变量的其实内存地址为0x1,那么0x1-0x4的值分别为0x61,0xFF,0xFF,0xFF.(位排列顺序)
3.char只占一个字节,因此会只读取0x61这个值.(数据类型的字节数,或者说大小)
4.0x61为十进制的97,对应ASCII表的话,代表的字符就是a,因此最终输出了a.(数据类型的解释方式)
可以看出,强制类型转换有的时候会让结果变得难以预料,因此这种技巧一般不太推荐使用,但是这种手段也确实是程序设计语言所必须的.
字符串的表示
这一点其实我们已经知道了,我们刚才知道了97其实代表的是字符’a’,而这个的由来就是根据ASCII表来表示的,我们在Linux系统上可以输入man ASCII命令来查看.
代码的表示
二进制如何的表示代码?
其实这些都是编译器的责任了,我们只需要写出像上面那个小程序一样的人们可以看懂的代码,编译器便会帮我们将其翻译成对应的机器所认识的二进制序列.从这个角度上,程序语言其实就是一个二进制序列的简单描述,它提供我们更简单的编写计算机可以执行的二进制序列的方法.
补充:
1.如何查看自己的OS是大端法还是小端法规则?
什么是大端小端?
比如0x11223344在大端机上是11223344,在小端机上是44332211,而一个机器是大端还是小端要看cpu类型以及运行在上面的操作系统。同一款cpu在不同的操作系统使用的大小端情况是不同的。当然我们通常使用的x86+windows是小端。
那如何测试大小端呢?
通常的技巧是使用一个指针:如:
int x = 1;
if(*(char *)&x == 1)
printf("little-endian\n");
else printf("big-endian\n");
2.字长
字长是CPU的主要技术指标之一,指的是CPU一次能并行处理的二进制位数,字长总是8的整数倍,通常PC机的字长为16位(早期),32位,64位。
PC机可以通过编程的方法来处理任意大小的数字,但数字越大,PC机就要花越长的时间来计算。PC机在一次操作中能处理的最大数字是由PC机的字长确定的。
3.虚拟空间是什么意思?
虚拟内存(Virtual Memory)是指计算机呈现出要比实际拥有的内存大得多的内存量。因此他允许程式员编制并运行比实际系统拥有的内存大得多的程式。这使得许多大型项目也能够在具有有限内存资源的系统上实现。一个非常恰当的比喻是:你不必非常长的轨道就能让一列火车从上海开到北京。你只需要足够长的铁轨(比如说3公里)就能完成这个任务。采取的方法是把后面的铁轨即时铺到火车的前面,只要你的操作足够快并能满足需求,列车就能象在一条完整的轨道上运行。这也就是虚拟内存管理需要完成的任务。在 Linux0.11 内核中,给每个程式(进程)都划分了总容量为 64MB 的虚拟内存空间。因此程式的逻辑地址范围是 0x0000000 到 0x4000000。有时我们也把逻辑地址称为 虚拟地址。因为和虚拟内存空间的概念类似,逻辑地址也是和实际物理内存容量无关的。逻辑地址和物理地址的“差距”是 0xC0000000,是由于虚拟地址->线性地址->物理地址映射正好差这个值。这个值是由操作系统指定的。机理逻辑地址(或称为虚拟地址)到线性地址是由CPU的段机制自动转换的。如果没有开启分页管理,则线性地址就是物理地址。如果开启了分页管理,那么系统程式需要参和线性地址到物理地址的转换过程。具体是通过设置页目录表和页表项进行的。
版权声明:本文为博主原创文章,未经博主允许不得转载。