- 浏览: 30610 次
最新评论
-
单证员:
一样的报错。跟这个没关系No result defined f ...
No result defined for action XXX and result XXX的问题在真相大白
动态储存管理小教程
2012-1-9
其实这个不是真正正统的教程(甚至可以认为是不务正业),因为我使用的语言是JASS语言 即魔兽争霸3地图编辑器的脚本语言。(好吧 我知道石头你又该嘲笑我了。确实 整这么长时间我一张成型的图也没拿出来。。。。但是,你不能否认我的技术!!)
但是其中算法和思想。其实和操作系统中储存管理是没啥区别的。在家各种冷,没事码码字活动活动僵硬的手指。
正好也尝试以浅显易懂的方式写一下自己小小的研究成果。
JASS这个语言是很简陋的(PASCAL风格)。他本身的数组全都固定是8192长度的。这样利用率非常的低。所以我就想做一套动态分配的数组。可以自定义数组长度提高利用率。所以我这个系统应运而生。
原理概要
jass中提供的数组都是8192长度。如此的规整因此我们可以把他看成一条内存。以下称作内存数组
实际上动态数组就是一个内存分配与回收的过程。就好比切一块长条的吐司面包。把最初是一块整的面包,逐渐切成根据具体需要的小块。而不是一口吞下。因此一个动态数组就是一个内存块。如何申请动态数组就是如何切面包的问题。(下文会交替使用内存块和面包块大家知道指的是一个东西就行)
首先。我要管理这个面包,我必须要了解他。这里我要给每块内存块记录一些信息。这样我才能更好的分配他们给大家吃。
控制信息如下。
struct block
{ int state;//1 :内存块状态----1=占用、 0=空闲
int initaddr;//2:内存块头地址 也就是这块空间在内存数组中的起始索引
int size;//3:内存块大小
int prev;//4:前相邻内存块索引(前指针)
int next;//5:后相邻内存块索引(后指针)
}
这里面我是用C语言的struct表示。在jass中其实就是以5个连续整数为整体放到数组中的,这个数组姑且叫做目录表。其实就是一个清单,纪录目前都有哪些面包块,都是多大。是分配给烧饼了还是分配给山炮了。或者是还没有分配。
然后这些控制信息节点在目录表中组成了一个双向链表。即知道自己的邻居在哪。能够方便的找到相邻的内存块。
1、申请
发面包了!首先我要从一大整块面包切一刀(这一大整块面包是目录表初始生成的一个节点)。然后把切出来的一块同时记录各种信息放到目录表里。然后分配给用户一个id 就像这样 integer a=allocArray(INT,200)
这样a就是一个200大小的整型数组了。这个a其实就是对应的内存块的控制信息节点的序号或者说索引。(因为我一个节点是包含5个数据的因此实际的索引是id*5再加上目录表初始位置)。这样就能通过目录表中的内存块信息。存取这段(数组)内存了。
存取就象这样 LoadInt(id,index);SaveInt(id,index,value)
2、回收
由于来面包party人比较多。你吃了面包,一定要自己再烤一块放到面包盘里供其他食用。否则后来到的人岂不是要饿死了。
做面包虽然复杂但对于咱们的内存块就简单多了。直接把内存块状态从占用改为空闲不就行了么!
真的这么简单?
确实是这么简单,但同时带来很大的问题。随着吃面包的人越来越多。原来那一整块面包被切的越来越碎。这样一来,后面如果来了饭量大的。每块面包都不够他吃但我们的规则又是只能拿一块。那么这个倒霉蛋是不是要注定饿死了。因此,当一个人吃完了之后又烤好新的放回去的时候。他要看下左右有没有别人新烤好放回去的面包,如果有,那他就应该回炉合并这两块或者三块面包组成一个更大块的面包。这样前面的杯具就不会发生了。这样就是释放做的最大的工作,合并相邻的空闲内存块。这里我的系统使用了一个策略即向右合并。即总是把左边的内存块合并到又边去,同时删掉左边的节点。如ABC三块A和C是空闲块。B是即将释放的。这是后会把A删掉各种信息合并到B里。变成B'C然后删掉B'把B'再合并到C里。
这样的好处是什么呢?因为我切面包总是从左开始切,那么那块面包剩余的部分(一般是较大的部分)总是在右边。这样的策略,总是能优先把内存释放到最大的那部分里。
不得不提的是,使用数组结构储存的另一大有点。上面所说的最大部分的面包在物理上总是存在数组的最左边的。而搜索可用内存块时又是从左边搜索。这样一来,分配总是从那块大的分配,而释放又总是释放到那块大的里面。这样就大大提高了内存的吞吐率,减少了内存碎片的产生。
3、存取
这个系统的重点就在于上述的分配与回首。当然存取也不得不提。
我这个系统内存理论上是没有长度限制的。我把多个数组拼成一个线性地址空间了。只需索引对8191取模即可 当然还需要分支语句使用这几个数组
具体看实际代码吧
4、相关技术
下面我讲一下目录表表本身的存储机制
其实我是把他们存到一个特殊的栈里的。这种栈只能在栈顶添加节点。但是允许在任意位置删除节点。
原理是,栈顶指针指的是逻辑上的栈顶并不代表是物理上的顶端。举例如下【sp:stack pointer 栈顶指针】
0 1 2 3 4 5 6 0 1 2 3 4 5 6 7 8
[1][2][3][4][0][0][0] 此时sp=4 如果删除第2个元素则变成 [1][(4)][3][4][0][0][0][0][0]
I I
这时sp=1即指向刚刚被删除的那个元素。但是刚才那个元素要储存上个栈顶的位置。用来指导sp的移动。
也即是说这种情况下,如果新数据到来,会存到第2个元素这里。同时sp为指向第2元素所指导的位置。即索引4.
以此类推。
当然需要说明的是。我的元素不止一个字段。必须有字段来表示这个位置是否是空的。
不然无法判断该位置是所存的4是业务数据还是栈顶指导数据。因此使用了块大小那个字段。
如果扫描节点时遇到一个节点的块大小是-1,则此节点是被删掉的节点,即【SP位移指导节点】(好吧自己胡乱起的名字)。
注:如果在需要位移指导时没有发现【SP位移指导节点】(其实就是初始的情况 如图1sp指的那个位置是个空节点没有指导信息)
。那么此时sp=sp+1。和普通栈一样。
这种算法的好处就是利用本身的字段空间储存一些信息。而不用进行栈顶元素以为补空。但是对于单元素节点的表不适合此方法。
5、struct vector 与类型检查
我的代码中并未提供struct解决方案(这里是想在jass语言里实现struct封装)
但是我基于这个系统可以提供一个思路。
如struct
{
int a;
int b;
real c;
unit u
}
则可以这样
integer a=allocArray(INT,2)//假设此时a=3
real b=allocArray(REAL,1)//b=11
unit u=allocArray(UNIT,1)//u=321
然后将a、b、u这3个数封装为一个数。即3210011003做为这个struct的id
然后使用这个id存取对用类型数据时再对这个数解析取出符合该类型的数就行了。
为什么要同时说下类型检查呢 因为思路和这个是一样
我的动态数组分配的id号本身是无法表示类型的如果你申请了个整型的数组id用来存取unit的起步乱套了。
因此同理 我把类型代号封装到这个id里 存取时先检查类型。
当然这些所谓的类型检查是运行期检查而已 ,很没有意义。
也正如struct的意义在于一个新的语言支持(像vj)一样。
这些东西,如果我做一个新语言,实现也就显得有那么些价值了。编译器类型检查也比这方便效率多了。
jass中提供的数组都是8192长度。如此的规整因此我们可以把他看成一条内存。以下称作内存数组
实际上动态数组就是一个内存分配与回收的过程。就好比切一块长条的吐司面包。把最初是一块整的面包,逐渐切成根据具体需要的小块。而不是一口吞下。因此一个动态数组就是一个内存块。如何申请动态数组就是如何切面包的问题。(下文会交替使用内存块和面包块大家知道指的是一个东西就行)
首先。我要管理这个面包,我必须要了解他。这里我要给每块内存块记录一些信息。这样我才能更好的分配他们给大家吃。
控制信息如下。
struct block
{ int state;//1 :内存块状态----1=占用、 0=空闲
int initaddr;//2:内存块头地址 也就是这块空间在内存数组中的起始索引
int size;//3:内存块大小
int prev;//4:前相邻内存块索引(前指针)
int next;//5:后相邻内存块索引(后指针)
}
这里面我是用C语言的struct表示。在jass中其实就是以5个连续整数为整体放到数组中的,这个数组姑且叫做目录表。其实就是一个清单,纪录目前都有哪些面包块,都是多大。是分配给烧饼了还是分配给山炮了。或者是还没有分配。
然后这些控制信息节点在目录表中组成了一个双向链表。即知道自己的邻居在哪。能够方便的找到相邻的内存块。
1、申请
发面包了!首先我要从一大整块面包切一刀(这一大整块面包是目录表初始生成的一个节点)。然后把切出来的一块同时记录各种信息放到目录表里。然后分配给用户一个id 就像这样 integer a=allocArray(INT,200)
这样a就是一个200大小的整型数组了。这个a其实就是对应的内存块的控制信息节点的序号或者说索引。(因为我一个节点是包含5个数据的因此实际的索引是id*5再加上目录表初始位置)。这样就能通过目录表中的内存块信息。存取这段(数组)内存了。
存取就象这样 LoadInt(id,index);SaveInt(id,index,value)
2、回收
由于来面包party人比较多。你吃了面包,一定要自己再烤一块放到面包盘里供其他食用。否则后来到的人岂不是要饿死了。
做面包虽然复杂但对于咱们的内存块就简单多了。直接把内存块状态从占用改为空闲不就行了么!
真的这么简单?
确实是这么简单,但同时带来很大的问题。随着吃面包的人越来越多。原来那一整块面包被切的越来越碎。这样一来,后面如果来了饭量大的。每块面包都不够他吃但我们的规则又是只能拿一块。那么这个倒霉蛋是不是要注定饿死了。因此,当一个人吃完了之后又烤好新的放回去的时候。他要看下左右有没有别人新烤好放回去的面包,如果有,那他就应该回炉合并这两块或者三块面包组成一个更大块的面包。这样前面的杯具就不会发生了。这样就是释放做的最大的工作,合并相邻的空闲内存块。这里我的系统使用了一个策略即向右合并。即总是把左边的内存块合并到又边去,同时删掉左边的节点。如ABC三块A和C是空闲块。B是即将释放的。这是后会把A删掉各种信息合并到B里。变成B'C然后删掉B'把B'再合并到C里。
这样的好处是什么呢?因为我切面包总是从左开始切,那么那块面包剩余的部分(一般是较大的部分)总是在右边。这样的策略,总是能优先把内存释放到最大的那部分里。
不得不提的是,使用数组结构储存的另一大有点。上面所说的最大部分的面包在物理上总是存在数组的最左边的。而搜索可用内存块时又是从左边搜索。这样一来,分配总是从那块大的分配,而释放又总是释放到那块大的里面。这样就大大提高了内存的吞吐率,减少了内存碎片的产生。
3、存取
这个系统的重点就在于上述的分配与回首。当然存取也不得不提。
我这个系统内存理论上是没有长度限制的。我把多个数组拼成一个线性地址空间了。只需索引对8191取模即可 当然还需要分支语句使用这几个数组
具体看实际代码吧
4、相关技术
下面我讲一下目录表表本身的存储机制
其实我是把他们存到一个特殊的栈里的。这种栈只能在栈顶添加节点。但是允许在任意位置删除节点。
原理是,栈顶指针指的是逻辑上的栈顶并不代表是物理上的顶端。举例如下【sp:stack pointer 栈顶指针】
0 1 2 3 4 5 6 0 1 2 3 4 5 6 7 8
[1][2][3][4][0][0][0] 此时sp=4 如果删除第2个元素则变成 [1][(4)][3][4][0][0][0][0][0]
I I
这时sp=1即指向刚刚被删除的那个元素。但是刚才那个元素要储存上个栈顶的位置。用来指导sp的移动。
也即是说这种情况下,如果新数据到来,会存到第2个元素这里。同时sp为指向第2元素所指导的位置。即索引4.
以此类推。
当然需要说明的是。我的元素不止一个字段。必须有字段来表示这个位置是否是空的。
不然无法判断该位置是所存的4是业务数据还是栈顶指导数据。因此使用了块大小那个字段。
如果扫描节点时遇到一个节点的块大小是-1,则此节点是被删掉的节点,即【SP位移指导节点】(好吧自己胡乱起的名字)。
注:如果在需要位移指导时没有发现【SP位移指导节点】(其实就是初始的情况 如图1sp指的那个位置是个空节点没有指导信息)
。那么此时sp=sp+1。和普通栈一样。
这种算法的好处就是利用本身的字段空间储存一些信息。而不用进行栈顶元素以为补空。但是对于单元素节点的表不适合此方法。
5、struct vector 与类型检查
我的代码中并未提供struct解决方案(这里是想在jass语言里实现struct封装)
但是我基于这个系统可以提供一个思路。
如struct
{
int a;
int b;
real c;
unit u
}
则可以这样
integer a=allocArray(INT,2)//假设此时a=3
real b=allocArray(REAL,1)//b=11
unit u=allocArray(UNIT,1)//u=321
然后将a、b、u这3个数封装为一个数。即3210011003做为这个struct的id
然后使用这个id存取对用类型数据时再对这个数解析取出符合该类型的数就行了。
为什么要同时说下类型检查呢 因为思路和这个是一样
我的动态数组分配的id号本身是无法表示类型的如果你申请了个整型的数组id用来存取unit的起步乱套了。
因此同理 我把类型代号封装到这个id里 存取时先检查类型。
当然这些所谓的类型检查是运行期检查而已 ,很没有意义。
也正如struct的意义在于一个新的语言支持(像vj)一样。
这些东西,如果我做一个新语言,实现也就显得有那么些价值了。编译器类型检查也比这方便效率多了。
globals
//内存地址空间
integer array IntArray1
integer array IntArray2
real array RealArray1
real array RealArray2
string array StrArray1
string array StrArray2
unit array UnitArray1
unit array UnitArray2
integer array ContentTable//目录表 记录着系统中内存块的信息。
//每块内存信息作为一个节点链式储存在这个数组中
integer CTRLINFO_LEN=5//内存块控制字段的长度 具体含义上面已经有解释了
integer MAXTYPE=4//这个是支持的类型的最大数目。系统会根据这个为每个类型划分目录表的存储区间
//目前只是用了几个常用类型如下 如果需要添加则增加MAXTYPE的值并添入类型代号即可 注意类型代号必须是依次的。
//例如我想加一个新类型timer 则定义integer TIMER=4
integer INT=0//代表整形
integer REAL=1
integer STRING=2
integer UNIT=3
integer test=1
endglobals
function initArray takes nothing returns nothing
//初始化内存块。也就是分配第一个内存块。
local integer atype=0
local integer initaddr
loop
exitwhen atype>=MAXTYPE
set initaddr=atype*R2I(8000/MAXTYPE)+atype
set ContentTable[initaddr]=1
set ContentTable[initaddr+2]=0
set ContentTable[initaddr+3]=16000 //寻址两个数组长度
set ContentTable[initaddr+4]=-1
set ContentTable[initaddr+5]=-1
set atype=atype+1
endloop
endfunctionfunction dump takes string str returns nothing
call DisplayTextToPlayer(GetLocalPlayer(),0,0,str)
endfunction
function searchBlock takes integer asklen,integer initaddr returns integer
//寻找第一个满足分配要求的内存块 大多数情况总会是第一块最大的。(因为他就在位置0)
local integer n=0
loop
exitwhen ContentTable[initaddr+n*CTRLINFO_LEN+3]==0
//因为不存在内存块大小为0的内存块(被删除的块大小为-1)因此遇到这个情况说明搜索到目录表物理末端了
if(ContentTable[initaddr+n*CTRLINFO_LEN+1]==0 and ContentTable[initaddr+n*CTRLINFO_LEN+3]>=asklen) then
return n
endif
set n=n+1
endloop
return -1
endfunction
function setPrevBlock takes integer initaddr,integer id,integer previd returns nothing
//设置内存块id的前接内存块为previd
set ContentTable[initaddr+id*CTRLINFO_LEN+4]=previd
endfunction
function getPrevBlock takes integer initaddr,integer id returns integer
//获取内存块id的前内存接块
return ContentTable[initaddr+id*CTRLINFO_LEN+4]
endfunction
function setNextBlock takes integer initaddr,integer id,integer nextid returns nothing
//设置 内存块id的后接内存块为nextid
set ContentTable[initaddr+id*CTRLINFO_LEN+5]=nextid
endfunction
function getNextBlock takes integer initaddr,integer id returns integer
//获取内存块id的后接内存块
return ContentTable[initaddr+id*CTRLINFO_LEN+5]
endfunction
function removeBlock takes integer id,integer atype returns nothing
//删除一个已经合并到其他空闲块的内存块。
local integer initaddr=atype*R2I(8000/MAXTYPE)+atype
local integer previd
local integer nextid
if(id==0) then
call dump("严重警告 第0块被删除了")//因为第0块其实是最右边的那块 因此不可能被删除
endif
if(ContentTable[initaddr+id*CTRLINFO_LEN+3]==0) then
call BJDebugMsg("无法删除一个不存在的节点")
return
else
set ContentTable[initaddr+id*CTRLINFO_LEN+1]=ContentTable[initaddr]
set ContentTable[initaddr+id*CTRLINFO_LEN+3]=-1//块大小变成-1 表示此节点变成sp位移指导节点
set ContentTable[initaddr]=id
//以下是标准的双向链表删除节点操作 学过数据结构的都应该清楚
//就是把前节点的后指针指向该节点的后节点
//把后节点的前指针指向该节点的前节点
set previd=getPrevBlock(initaddr,id)
set nextid=getNextBlock(initaddr,id)
if(previd>=0) then
call setNextBlock(initaddr,previd,nextid)
endif
if(nextid>=0) then
call setPrevBlock(initaddr,nextid,previd)
endif
endif
endfunction
function allocArray takes integer len,integer atype returns integer
local integer initaddr=atype*R2I(8000/MAXTYPE)+atype
local integer nid=-1
local integer bid=searchBlock(len,initaddr)//找到待切的蛋糕
if(bid==-1) then
call MemDefrag(atype)
//内存不足
call dump("内存不足!")
else
set nid=ContentTable[initaddr]
if(nid>8000/MAXTYPE/5) then
call dump("内存块数量溢出!")
return -1
endif
if(ContentTable[initaddr+nid*CTRLINFO_LEN+1]==0) then//处理无sp指导信息时的sp步进位移
set ContentTable[initaddr]=ContentTable[initaddr]+1
else
set ContentTable[initaddr]=ContentTable[initaddr+nid*CTRLINFO_LEN+1]//否则sp位移到指导信息所标识位置
endif
set ContentTable[initaddr+nid*CTRLINFO_LEN+1]=1
set ContentTable[initaddr+nid*CTRLINFO_LEN+2]=ContentTable[initaddr+bid*CTRLINFO_LEN+2]
set ContentTable[initaddr+nid*CTRLINFO_LEN+3]=len
set ContentTable[initaddr+nid*CTRLINFO_LEN+4]=ContentTable[initaddr+bid*CTRLINFO_LEN+4]
if(ContentTable[initaddr+bid*CTRLINFO_LEN+4]!=-1) then
set ContentTable[initaddr+ContentTable[initaddr+bid*CTRLINFO_LEN+4]*CTRLINFO_LEN+5]=nid
endif
set ContentTable[initaddr+nid*CTRLINFO_LEN+5]=bid
//set test=test+1
if(ContentTable[initaddr+bid*CTRLINFO_LEN+3]-len==0) then//如果正好分配完 老内存块删掉
call removeBlock(bid,atype)
else
set ContentTable[initaddr+bid*CTRLINFO_LEN+2]=ContentTable[initaddr+bid*CTRLINFO_LEN+2]+len
set ContentTable[initaddr+bid*CTRLINFO_LEN+3]=ContentTable[initaddr+bid*CTRLINFO_LEN+3]-len
set ContentTable[initaddr+bid*CTRLINFO_LEN+4]=nid
endif
endif
return nid
endfunction
function destroyArray takes integer id,integer atype returns nothing
local integer initaddr=atype*R2I(8000/MAXTYPE)+atype
local integer flag=4
local integer nid
set ContentTable[initaddr+id*CTRLINFO_LEN+1]=0
set nid=ContentTable[initaddr+id*CTRLINFO_LEN+4] //合并前接空闲块
if(nid>=0 and ContentTable[initaddr+nid*CTRLINFO_LEN+1]==0) then
set ContentTable[initaddr+id*CTRLINFO_LEN+2]=ContentTable[initaddr+nid*CTRLINFO_LEN+2]
set ContentTable[initaddr+id*CTRLINFO_LEN+3]=ContentTable[initaddr+id*CTRLINFO_LEN+3]+ContentTable[initaddr+nid*CTRLINFO_LEN+3]
set ContentTable[initaddr+id*CTRLINFO_LEN+4]=ContentTable[initaddr+nid*CTRLINFO_LEN+4]
call removeBlock(nid,atype)
endif
set nid=ContentTable[initaddr+id*CTRLINFO_LEN+5]//合并后接空闲快
if(nid>=0 and ContentTable[initaddr+nid*CTRLINFO_LEN+1]==0) then
set ContentTable[initaddr+nid*CTRLINFO_LEN+2]=ContentTable[initaddr+id*CTRLINFO_LEN+2]
set ContentTable[initaddr+nid*CTRLINFO_LEN+3]=ContentTable[initaddr+nid*CTRLINFO_LEN+3]+ContentTable[initaddr+id*CTRLINFO_LEN+3]
set ContentTable[initaddr+nid*CTRLINFO_LEN+4]=ContentTable[initaddr+id*CTRLINFO_LEN+4]
call removeBlock(id,atype)
endif
endfunction
function CountRealIndex takes integer atype,integer id,integer index returns integer
//计算真实的索引
local integer initaddr=atype*R2I(8000/MAXTYPE)+atype
local integer loc=ContentTable[initaddr+id*CTRLINFO_LEN+2]+index
if(index>=ContentTable[initaddr+id*CTRLINFO_LEN+3] or ContentTable[initaddr+id*CTRLINFO_LEN+1]==0) then
call dump("非法的索引:"+I2S(index))
endif
return loc
endfunction
function LDI takes integer id,integer index returns integer //loadinteger
local integer loc=CountRealIndex(INT,id,index)
if(loc>=8100) then
return IntArray2[loc-8100]
else
return IntArray1[loc]
endif
endfunction
function SVI takes integer id,integer index,integer val returns nothing //saveinteger
local integer loc=CountRealIndex(INT,id,index)
if(loc>=8100) then
set IntArray2[loc-8100]=val
else
set IntArray1[loc]=val
endif
endfunction
//内存地址空间
integer array IntArray1
integer array IntArray2
real array RealArray1
real array RealArray2
string array StrArray1
string array StrArray2
unit array UnitArray1
unit array UnitArray2
integer array ContentTable//目录表 记录着系统中内存块的信息。
//每块内存信息作为一个节点链式储存在这个数组中
integer CTRLINFO_LEN=5//内存块控制字段的长度 具体含义上面已经有解释了
integer MAXTYPE=4//这个是支持的类型的最大数目。系统会根据这个为每个类型划分目录表的存储区间
//目前只是用了几个常用类型如下 如果需要添加则增加MAXTYPE的值并添入类型代号即可 注意类型代号必须是依次的。
//例如我想加一个新类型timer 则定义integer TIMER=4
integer INT=0//代表整形
integer REAL=1
integer STRING=2
integer UNIT=3
integer test=1
endglobals
function initArray takes nothing returns nothing
//初始化内存块。也就是分配第一个内存块。
local integer atype=0
local integer initaddr
loop
exitwhen atype>=MAXTYPE
set initaddr=atype*R2I(8000/MAXTYPE)+atype
set ContentTable[initaddr]=1
set ContentTable[initaddr+2]=0
set ContentTable[initaddr+3]=16000 //寻址两个数组长度
set ContentTable[initaddr+4]=-1
set ContentTable[initaddr+5]=-1
set atype=atype+1
endloop
endfunctionfunction dump takes string str returns nothing
call DisplayTextToPlayer(GetLocalPlayer(),0,0,str)
endfunction
function searchBlock takes integer asklen,integer initaddr returns integer
//寻找第一个满足分配要求的内存块 大多数情况总会是第一块最大的。(因为他就在位置0)
local integer n=0
loop
exitwhen ContentTable[initaddr+n*CTRLINFO_LEN+3]==0
//因为不存在内存块大小为0的内存块(被删除的块大小为-1)因此遇到这个情况说明搜索到目录表物理末端了
if(ContentTable[initaddr+n*CTRLINFO_LEN+1]==0 and ContentTable[initaddr+n*CTRLINFO_LEN+3]>=asklen) then
return n
endif
set n=n+1
endloop
return -1
endfunction
function setPrevBlock takes integer initaddr,integer id,integer previd returns nothing
//设置内存块id的前接内存块为previd
set ContentTable[initaddr+id*CTRLINFO_LEN+4]=previd
endfunction
function getPrevBlock takes integer initaddr,integer id returns integer
//获取内存块id的前内存接块
return ContentTable[initaddr+id*CTRLINFO_LEN+4]
endfunction
function setNextBlock takes integer initaddr,integer id,integer nextid returns nothing
//设置 内存块id的后接内存块为nextid
set ContentTable[initaddr+id*CTRLINFO_LEN+5]=nextid
endfunction
function getNextBlock takes integer initaddr,integer id returns integer
//获取内存块id的后接内存块
return ContentTable[initaddr+id*CTRLINFO_LEN+5]
endfunction
function removeBlock takes integer id,integer atype returns nothing
//删除一个已经合并到其他空闲块的内存块。
local integer initaddr=atype*R2I(8000/MAXTYPE)+atype
local integer previd
local integer nextid
if(id==0) then
call dump("严重警告 第0块被删除了")//因为第0块其实是最右边的那块 因此不可能被删除
endif
if(ContentTable[initaddr+id*CTRLINFO_LEN+3]==0) then
call BJDebugMsg("无法删除一个不存在的节点")
return
else
set ContentTable[initaddr+id*CTRLINFO_LEN+1]=ContentTable[initaddr]
set ContentTable[initaddr+id*CTRLINFO_LEN+3]=-1//块大小变成-1 表示此节点变成sp位移指导节点
set ContentTable[initaddr]=id
//以下是标准的双向链表删除节点操作 学过数据结构的都应该清楚
//就是把前节点的后指针指向该节点的后节点
//把后节点的前指针指向该节点的前节点
set previd=getPrevBlock(initaddr,id)
set nextid=getNextBlock(initaddr,id)
if(previd>=0) then
call setNextBlock(initaddr,previd,nextid)
endif
if(nextid>=0) then
call setPrevBlock(initaddr,nextid,previd)
endif
endif
endfunction
function allocArray takes integer len,integer atype returns integer
local integer initaddr=atype*R2I(8000/MAXTYPE)+atype
local integer nid=-1
local integer bid=searchBlock(len,initaddr)//找到待切的蛋糕
if(bid==-1) then
call MemDefrag(atype)
//内存不足
call dump("内存不足!")
else
set nid=ContentTable[initaddr]
if(nid>8000/MAXTYPE/5) then
call dump("内存块数量溢出!")
return -1
endif
if(ContentTable[initaddr+nid*CTRLINFO_LEN+1]==0) then//处理无sp指导信息时的sp步进位移
set ContentTable[initaddr]=ContentTable[initaddr]+1
else
set ContentTable[initaddr]=ContentTable[initaddr+nid*CTRLINFO_LEN+1]//否则sp位移到指导信息所标识位置
endif
set ContentTable[initaddr+nid*CTRLINFO_LEN+1]=1
set ContentTable[initaddr+nid*CTRLINFO_LEN+2]=ContentTable[initaddr+bid*CTRLINFO_LEN+2]
set ContentTable[initaddr+nid*CTRLINFO_LEN+3]=len
set ContentTable[initaddr+nid*CTRLINFO_LEN+4]=ContentTable[initaddr+bid*CTRLINFO_LEN+4]
if(ContentTable[initaddr+bid*CTRLINFO_LEN+4]!=-1) then
set ContentTable[initaddr+ContentTable[initaddr+bid*CTRLINFO_LEN+4]*CTRLINFO_LEN+5]=nid
endif
set ContentTable[initaddr+nid*CTRLINFO_LEN+5]=bid
//set test=test+1
if(ContentTable[initaddr+bid*CTRLINFO_LEN+3]-len==0) then//如果正好分配完 老内存块删掉
call removeBlock(bid,atype)
else
set ContentTable[initaddr+bid*CTRLINFO_LEN+2]=ContentTable[initaddr+bid*CTRLINFO_LEN+2]+len
set ContentTable[initaddr+bid*CTRLINFO_LEN+3]=ContentTable[initaddr+bid*CTRLINFO_LEN+3]-len
set ContentTable[initaddr+bid*CTRLINFO_LEN+4]=nid
endif
endif
return nid
endfunction
function destroyArray takes integer id,integer atype returns nothing
local integer initaddr=atype*R2I(8000/MAXTYPE)+atype
local integer flag=4
local integer nid
set ContentTable[initaddr+id*CTRLINFO_LEN+1]=0
set nid=ContentTable[initaddr+id*CTRLINFO_LEN+4] //合并前接空闲块
if(nid>=0 and ContentTable[initaddr+nid*CTRLINFO_LEN+1]==0) then
set ContentTable[initaddr+id*CTRLINFO_LEN+2]=ContentTable[initaddr+nid*CTRLINFO_LEN+2]
set ContentTable[initaddr+id*CTRLINFO_LEN+3]=ContentTable[initaddr+id*CTRLINFO_LEN+3]+ContentTable[initaddr+nid*CTRLINFO_LEN+3]
set ContentTable[initaddr+id*CTRLINFO_LEN+4]=ContentTable[initaddr+nid*CTRLINFO_LEN+4]
call removeBlock(nid,atype)
endif
set nid=ContentTable[initaddr+id*CTRLINFO_LEN+5]//合并后接空闲快
if(nid>=0 and ContentTable[initaddr+nid*CTRLINFO_LEN+1]==0) then
set ContentTable[initaddr+nid*CTRLINFO_LEN+2]=ContentTable[initaddr+id*CTRLINFO_LEN+2]
set ContentTable[initaddr+nid*CTRLINFO_LEN+3]=ContentTable[initaddr+nid*CTRLINFO_LEN+3]+ContentTable[initaddr+id*CTRLINFO_LEN+3]
set ContentTable[initaddr+nid*CTRLINFO_LEN+4]=ContentTable[initaddr+id*CTRLINFO_LEN+4]
call removeBlock(id,atype)
endif
endfunction
function CountRealIndex takes integer atype,integer id,integer index returns integer
//计算真实的索引
local integer initaddr=atype*R2I(8000/MAXTYPE)+atype
local integer loc=ContentTable[initaddr+id*CTRLINFO_LEN+2]+index
if(index>=ContentTable[initaddr+id*CTRLINFO_LEN+3] or ContentTable[initaddr+id*CTRLINFO_LEN+1]==0) then
call dump("非法的索引:"+I2S(index))
endif
return loc
endfunction
function LDI takes integer id,integer index returns integer //loadinteger
local integer loc=CountRealIndex(INT,id,index)
if(loc>=8100) then
return IntArray2[loc-8100]
else
return IntArray1[loc]
endif
endfunction
function SVI takes integer id,integer index,integer val returns nothing //saveinteger
local integer loc=CountRealIndex(INT,id,index)
if(loc>=8100) then
set IntArray2[loc-8100]=val
else
set IntArray1[loc]=val
endif
endfunction
相关推荐
总的来说,这个“简单的动态网页教程”会涵盖动态网页开发的各个环节,包括前端基础、服务器端编程、数据库管理和客户端交互。无论你是完全的初学者还是希望巩固基础,这个教程都能提供有价值的指导。通过学习,你...
本资源聚焦于第八章——动态存储管理,这是编程中至关重要的一个环节,因为程序在运行时往往需要灵活地分配和释放内存。 动态存储管理主要涉及以下几个关键知识点: 1. **堆内存与栈内存**:在C语言中,内存分为栈...
Oracle数据库管理员教程旨在引导读者掌握如何管理和控制Oracle数据库系统,这一关键角色被称为DBA(Database Administrator)。DBA的职责广泛,包括理解Oracle数据库的体系结构、安装和升级数据库管理系统、控制...
Oracle数据库管理是计算机科学中数据库管理系统领域的重要分支。...通过Oracle数据库管理教程的学习,数据库管理员可以获得数据库设计、管理、备份与恢复等方面的全方位知识,为日常工作中遇到的各种问题提供解决方案。
在本教程中,你将学习到如何管理和监控服务器、存储设备、网络设备,以及操作系统的基本操作和配置。 二、软件开发与运维结合 随着DevOps理念的普及,软件开发与运维之间的界限日益模糊。本教程会介绍如何进行有效...
Excel VBA 学生成绩管理系统教程旨在教读者如何利用VBA(Visual Basic for Applications)编程语言,构建一个高效的学生考试成绩管理系统。这个系统能够自动化处理大量数据,减轻学校管理工作的负担,提高工作效率。...
教程名称:存储和文件管理解析系列视频教程课程目录:【】01:磁盘分区形式【】02.卷的管理【】03.动态磁盘管理【】04.设置ISCSI网络磁盘【】05:管理VHD【】06:管理NTFS权限【】07.EFS加密【】08:EFS恢复代理【】...
联想ThinkSystem DE系列存储配置教程 联想ThinkSystem DE系列存储配置教程是联想提供的存储配置解决方案,旨在帮助用户快速配置存储系统。本教程涵盖了DE系列存储的基本配置、池和卷组的创建、工作负载的定义、卷的...
MySQL则是一种流行的关系型数据库管理系统(RDBMS),用于存储和管理网站的数据。通过学习MySQL,你将了解如何创建数据库、表,并掌握SQL语言,包括数据的插入、查询、更新和删除操作。本书将引导你进行数据库设计,...
本项目是基于微信小程序的云开发实现的一个图书管理小程序,利用了微信提供的云端能力,包括数据库、存储和计算等服务。 云开发(Tencent Cloud Base,TCB)是微信小程序生态中的一项重要服务,它为开发者提供了...
12:配置和管理存储库(SR) 13:创建、使用资源池(Resource Pool) 14:使用vApp 15:配置高可用性(Hight Availablity) 16:虚拟机保护和恢复(VMPR) 17:配置和管理License 服务器 18:系统性能监视
总的来说,通过学习“mysql经典教程+mysql存储过程讲解”,你不仅可以掌握MySQL的基础操作,还能深入了解如何利用存储过程、触发器和游标来实现更复杂的数据管理策略。这将有助于你成为一名更高效的数据库管理员或...
《Windows Server 2008 管理教程》涵盖了多方面的内容,旨在帮助管理员有效管理和维护Windows Server 2008操作系统。本教程重点介绍了以下几个核心知识点: 1. **配置终端服务**:Windows Server 2008的终端服务...
通过学习本教程,你将能够熟练地创建、调用和管理Oracle存储过程,解决复杂的业务问题,提升数据库应用的效率和安全性。教程中的20篇文档将覆盖这些知识点的详细解释和示例,帮助你逐步成为Oracle存储过程的专家。
本教程“动态网站教程(PPT)”旨在帮助初学者理解动态网站的工作原理并掌握开发技能。 在动态网站的开发中,ASP.NET是一个非常重要的平台,尤其是3.5版本,它结合了.NET Framework的强大功能和C#编程语言的简洁性...
MySQL存储过程是数据库管理系统中的一种重要功能,它允许开发者预编译一组SQL语句并封装成一个可重复使用的对象。这个经典教程旨在深入探讨存储过程的各个方面,帮助读者掌握这一强大的数据库编程工具。 1. **存储...