第十章 类、对象与实现(一)

2015-01-27 06:28:22 · 作者: · 浏览: 117
第十章 类、对象与实现

万物都是容器,容器的符号是U;对象就是单个容器的别名。一切皆对象,具有某些相同属性特征的对象归纳成类。对象(Object)是类(Class)的一个实例(Instance);类是对象的模板。如果将对象比作房子,那么类就是房子的蓝图。我们以自然语言去描述世界,而计算机是用各种数据结构去描述世界;数据可以用x个二进制位的位容器BUx来表示。对象具有状态、方位、时间等属性,每个属性是用数据值或说位容器BUx来描述的;最后构造了描述对象属性的数据表。对象还有操作,用于改变对象的属性,操作就是对象的行为。操作也可以说是方法或函数,方法就是一段指令;一条指令就是一个数据字W;所以,方法就是一个指令字数组。由一系列方法构成的指令数据表就描述了对象的行为。所以,一个对象就是由属性数据表和方法数据表来描述的。或者换句话说对象实现了数据和操作的结合,使数据和操作封装于对象的统一体中。在APO中,数据表不外是一个行(8字)对齐的位容器,是一段32字节或8字对齐的存储空间;和C语言的结构struct类同。数据表中的元素最小是一个字(4字节、2字符),并最终是行对齐。

一、变量与表

1、变量

变量:用来标识(identify)一块内存区域,这块区域的内容是可变的。
变量名:是一个标识符(identifier),用来指代一块内存区域,即变量。

CPU是用内存地址来标识一块内存区域的,机器码中,是不会出现变量名的;出现的是逻辑相对地址。在汇编层次上,操作的都是地址,不存在任何名称;变量名是给我们程序员操作内存来使用的。简单说,变量就是地址;在对程序编译连接时由编译系统给每一个变量名分配相应的内存逻辑地址,形成符号表;该地址分配后不可改变。当你调用此变量时,编译器就会根据此符号表找到对应的逻辑地址,然后翻译成汇编指令。例如从R2寄存器为基址的表中读一个变量所在行的内容到行寄存器H5,翻译成汇编:MOV H5, R2(变量逻辑相对地址); 结果是:( R2 + 变量逻辑相对地址变量 ) -> H5。在一个数据表中的逻辑相对地址是指变量地址与表初始地址0的相对偏移,编译器把逻辑相对地址、变量名、变量类型等一起放在符号表中。所以,变量名并不占内存空间;在APO中,变量逻辑相对地址(简称变量地址)是一个字1W。


程序是由一些类文件组成,一个具体类就产生2张表(属性表、方法表)。在编译时产生的类文件的2张表都是从地址0开始的,在连接时将各个类文件进行符号替换,这时会修改相应的表逻辑相对地址,最后产生一个2张合表都从地址0开始的程序文件。在运行时加载器会把程序的2张合表加载到某个不定的内存区域中(每次加载到的物理内存地址不一定相等)。当然,许多类属性表或方法表合作一起,程序运行时还有很多对象在参与;应该有一个方向指示总表:对象头列表。对象属性表、方法表或全局变量或静态变量(包括一些复杂类型的常量),它们所需要的空间大小可以明确计算出来,并且不会再改变,因此它们可以直接存放在程序文件的特定的节里(而且包含初始化的值),程序运行时也是直接将这个节加载到内存中,不必在程序运行期间用额外的代码来产生这些变量和初始值。其实在运行期间再看“变量”这个概念就不再具备编译期间那么多的属性了(诸如名称,公有、私有等类型,作用域,生存期等等),对应的只是一块内存(只有首址和大小),所以在运行期间动态申请的空间,是需要额外的代码维护,以确保不同变量不会混用内存。

一个程序有大量的属性表、代码表、属性元素、数组、字符串等等变量;CPU不认识变量名字,只认识地址数字;所以,编译器的任务之一就是把各种变量名称翻译成相应的地址了。变量所指元素的数据类型在许多语言中都有很多说法,如整形,单精度,双精度,字符串等等。真实情形是CPU不认识它们,只是编译器、方法(代码段)认识它们。APO是汇编语言,没那么多说法。简单地,APO变量的数据类型只有位容器;但需考虑对齐。表不外是x行容器BUxH,表中的元素或许是一个字,或是32位对齐的位图,或是字(字符)数组,或是一个表,或是另一个表的引用(动态数组,动态字符串,表)。一个数据块是64KH(行)= 1MZ(字符)= 512KW(字)= 2MB(字节)。

2、内存区域

APO在内存分配时会涉及到以下区域:

◆寄存器:用于自动变量,如方法里的局部变量。

◆栈:存放临时的基本类型数据、方法的局部变量。

对象的引用(包括动态对象)、动态分配内存的变量引用,局部静态变量等都应该保存在对象的属性表中;但动态对象、动态变量的身体是存放在堆中。栈是会自动清除或覆盖数据的,适合自动变量。APO对堆内存的操作和对栈内存的操作速度是一样的,比寄存器操作稍微慢点;即使是动态内存分配和释放(有专用的硬件指令)不过是几个ns。

当在一段代码块定义一些变量时,编译器就在栈中为这些变量分配内存空间,当这些变量退出该作用域后,栈会自动释放掉(重新设置栈SP寄存器值)为这些变量所分配的内存空间,该内存空间可以立即被另作他用。栈中的数据大小和生命周期是可以确定的。

在APO中,一个进程内只有一个用户栈,所有的对象(包括线程对象)都是共用一个栈。栈的大小,是全局变量;默认是2个页,256行;如果对象数目多或很多线程,就可设大些;最大是32KH。为何JAVA等是每个线程需要一个2MB栈,大量占用内存?我认为这是设计问题。在APO中,对象消息处理代码块内只是一些少量的自动变量;普通对象完成消息处理后就会自动清除,相对来说不占空间;而线程对象可能会因等待消息,从而暂时让出CPU,但并不让出占据栈内的那部分空间;就可能会同时有多个线程占据栈空间。解决办法就是增大栈空间吧;APO中一个进程就算有6万个对象在活动,半个数据块的栈空间就足够了。编译器将自动变量翻译成对栈指针(0地址)的相对偏移地址。

◆堆:存放用动态产生的数据

堆内存用来存放由动态创建的对象和数组等动态变量。在堆中分配的内存,由APO的内存管理员来管理或由用户代码释放。动态声明(用DYV标识符)一个变量,DYVBU64KH A;那么系统就会自动为变量A分配一个数据块,64KH行的内存空间,并返回首指针,大小给代码保存好。这时,在编译器的符号表中没有变量A的相对地址;而是需要另外的代码通过保存在对象属性表中的首指针去操作变量。声明BU1W[512K] A;字数组 或者BU1Z [1M]A;百万个字符串数组;实际上也是分配一个数据块。定义向量或说动态数组BUxW[]也是可以的,可能会使用到连续的数据块或数据块链表。但内存总是有限的,还不如用数据流,只是开辟一个数据块窗吧;数据流可以无限。动态内存的生存期由程序员决定,使用非常灵活,但如果分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏,频繁地分配和释放不同大小的空间将会产生内存碎块。当然,在APO中系统内存管理员也会定期清理垃圾并回收内存。对象的析构方法也会据对象引用计数为0时释放相应的对象内存。

   在代码块中以表声明、或在代码块和类中以向量声明、DYV声明的变量都放到堆区,都当作是动态对象;系统会返回分配的块号、行数、行开始地址。并将后3项保存到动态对象头列表相应项中。由于动态变量的地址,编译器无法预先定义;所以是不会把动态变量翻译成地址的