高级程序语言中变量的数据类型表示的意义是什么?

数据类型有两个作用

  • 占用内存空间的大小

  • 如何解释内存数据


    变量是内存地址的抽象

使用栈结构很好的解决了函数调用(在执行前将函数指令的下一个地址入栈,指令执行完毕,地址出栈,CPU即可知道下一个指令的地址)和内存地址管理,后调的函数必然先销毁,所以对应的临时变量(内存地址)也需要先回收(变量的生命周期[GC的引用计数])。这就是栈!

队列

协调好输入与处理时机之间的关系,排队的机制是很方便的。

在内存上,实现这种机制的方式就是队列。当我们需要处理通讯中发送的数据时,或由同时运行的多个程序所发送过来的数据时,会用到这种对队列中存储的不规则数据进行处理的方法。

队列一般是以环状缓冲区(ring buffer)的方式来实现的。

排成纵队,从队头出队,从队尾入队,随着不停的出队,由于队伍没有整体前移,腾出来的无效内存越来越多。排成环队,队尾追队头,随着出队,腾出来的内存可被用来入队,节约内存。

今天看完了《程序是怎样跑起来的》这本书,总算对程序以及程序背后的运行机制的概念有了一些新的认识,也消除了一些疑问。

为什么计算机是二进制?

数据在计算机的存储,执行,在芯片内部都是一个个电路开关,0与1实际上是物理电信号高低电位的映射。为了解决负数计算如1 + (-1),于是产生了补码。二进制与十进制,都是对同一种数据的不同表达形式,而计算机内部使用二进制,是与其内部构造使用高低电位来表达两种状态而选择的最恰当的方式。

补码从何而来?

补码从何而来?存在即合理,不如换个角度思考,补码的存在是为了解决什么问题。

它的存在是为了解决计算机的减法问题。

举个简单的例子,我们都知道1 + (-1) == 0,然而二进制该怎样表达这个等式呢?我们都知道计算机只能执行加法运算,也知道减法在计算机中也是加法,只是加了负数。可是,这样的负数如何才能得到呢?有人会说二进制数有符号位,正数的符号为为0,负数为1-1可以表示成1000 0001,但是显然,0000 0001 + 1000 0001 != 0000 0000。这样的计算结果是错误的 。所以我们需要一种新的表达方式,利用计算溢出,于是有了补码

1的二进制表示为0000 0001-1?表示,则有0000 0001 + ? == 0000 0000

那么利用计算机的最高位溢出特性,显然可以知道-1的补码为1111 1111

如此,0000 0001 + 1111 1111 == 0000 0000就成立了,因为高位的1被舍弃了

更一般的,设二进制数xx > 0,则-x的补码为!x + 1,也就是x的各位取反后加1。

1为例,1的二进制为0000 0001,根据以上公式我们可得-1 == !(1) + 1 == 1111 1110 + 1 == 1111 1111

如此就得出了负数的补码。

那么正数呢?

我们已经知道,补码的发明,解决了计算机的减法问题,解决了负数在计算机的表达问题。

对于正数,补码就是其本身。

浮点数

应对生活中的小数,计算机也有浮点数表达方式。在32位的数据中,IEEE规定了浮点数的表达方式,其中1位符号位,8位指数位23位尾数。这曾经浮点数也让我产生了一些错误认知,我一度以为它在计算机内可以表示任意数值,因为数学告诉我在非0长度的区间,数应该是无限的。但现在才能理解,32位数据这意味着其表示范围只有2^32个数,并不是无限的。二进制浮点数不可能表示任意小数,它也面临着精度问题,就像十进制小数也不能准确表达1/3一样。

编码

由于计算机只能处理数字,所以人类生活中的字符,符号,全都靠的是编码映射。这也是计算机擅长的处理方式。

调用栈

函数的调用栈,目的是切换指令的上下文环境,解决了程序执行顺序的问题。也帮助了寄存器清除局部变量。

硬盘

内存造价昂贵,数据读取速度快,而硬盘较为低廉,数据读取速度慢。但较为低廉这意味着我们花同样的价钱,可以买到容量较大的硬盘。我们可以把文件、程序存储在硬盘,需要用到时再读取内存让cpu运行计算。
扇区是硬盘物理读写的最小单位,一个扇区一般为512字节。在windows中,硬盘读写单位是扇区的整数倍,称为簇,不同的文件不能存放在同一簇中。因此不管是多小的文件,都会占用1簇的空间。所以你会发现如果一个文件夹里有非常多的细碎小文件并且总共容量很大的话,无论是对这个文件夹进行复制或是删除操作,花费的时间都相对漫长,但如果把文件夹打包成压缩包形成一个整体再进行复制或是删除操作,速度将大大加快。

数据压缩

数据含有重复表达的信息,这意味着浪费了内存,于是为了降低这样的浪费,就有人提出了数据压缩。数据压缩分为可逆压缩和非可逆压缩。比如图片有损压缩方面,如jpg格式等,压缩数据基本不会影响图片效果,同里也有音频的mp3格式等。但文本数据以及程序则必须使用可逆压缩,否则原有的信息缺失将无法得到正确的表达。哈夫曼编码则是经典的可逆压缩算法。

程序跨平台

程序不能直接操作硬件,而是调用操作系统提供的接口进行间接操作。不同CPU架构的指令集可能不同,这也就意味着编译出的程序在不同操作系统下可能无法正常运行,因为指令集无法正确被其CPU处理,于是延伸出了程序如何实现跨平台运行的问题。跨平台有两种方式,源码级别的跨平台以及虚拟环境的跨平台。源码级别的跨平台,在Unix的FreeBSD存在一种叫做Ports的机制,能够根据当前的运行环境来编译出对应系统能够运行的本地代码。简称一次编写,到处编译。
这种方式略微繁琐,于是又出现了虚拟运行环境的东西,如Java的虚拟机。Java编译后会产生统一的字节码,我的理解是字节码就相当于为指令集提供了一种抽象,而Java在不同操作系统都提供了Java虚拟机,它负责把字节码转变为对应架构的指令集,转译成本地代码。相当于Java虚拟机代替我们完成了Ports的机制处理,实现了一次编写,到处运行。然而Java的二次转译也意味着同样流程的程序分别使用C/C++与Java编写,执行效率一般来说不会超过C/C++这样不需要经过二次转译的语言。

动态链接库

程序也占内存,浩如烟海的程序中往往也有很多实现了同样逻辑的api。这样数据就造成了冗余,浪费了内存。于是在windows中,动态链接库DLL解决了这样的问题。只需要提供了完成某些功能封装好的api,开发者只需要使用其api即可顺利完成其功能,并且所有需要实现此功能的程序都可以共同使用这个DLL,这也就减少了内存浪费。并且可以做到功能的局部更新(只需要更新相应的DLL文件即可)。

伪随机数

随机数也并不是真随机,而是借助计算公式得出。如果提供一致的参数,理论上可以推演出下一步的结果。

中断信号

中断是异步编程的通信方式。CPU一旦接到中断型号,就会把寄存器中所有的数据保存在内存的栈中,然后执行中断处理程序。执行中断程序完毕后,则将栈中数据弹出。这样就完成了上下文的切换。而为了防止多个设备同时进行中断请求,所以计算机引入了中断控制器来进行缓冲,将中断请求有序的传递给CPU。


看完这本书,大概花了我大半个月的时间,我几乎是在这本书中重新学了一遍计算机组成原理。我知道这本书里面的很多内容其实没有细致的讲解,但至少让我对程序是如何跑起来这一个概念有了一些认识,我觉得还是有所收获的。面对计算机底层知识,也不会再让我恐惧。

大一大二时我一直不太理解为什么我一个学习软件的,还要去了解硬件层面的知识。现在我大概明白,我曾经追求的各种新鲜技术,各种的猎奇。这只不过是学习上层提供的各种api,拿着他们已经做好的工具,去搭积木。提供积木的品牌很多,但仅仅局限于使用这样的积木,自始自终,我也不会有朝一日实现自己积木的那一天。
上层的工具时刻都在变化,而不便的,永远是下层建筑最经典的智慧。

或许这篇日后还会更新呢? 我也很期待~

碎碎话

每一次伟大的认知,都源于对这个世界的不解。

2022年5月22日于寝室