过完年了,又开始新的学习和生活了。去年用了不少时间把《汇编语言》学习了一下,获益匪浅!十分感谢汇编网各位老师和网友对我的帮助。现在,俺开始跟着学习C语言了。吼吼~~
不过,在学习“函数malloc的实现”这一章的过程中,俺发现教程中提供的参考代码中,某些细节之处存在小小的纰漏,在此我就大胆一下,对本章中的学习过程进行一下探索和探讨,望各位老师和同学不吝指教。
首先,根据对教程的理解,我先将整个malloc申请内存引发的流程简单说一下。
1.用户通过malloc申请内存,开始遍历空间链表,查找合适大小的结点。
2.如果找到合适大小的结点,就将该结点返回给用户。
3.如果空间链表中没有合适大小的内存快,就调用morecore函数,在未映射堆进行内存开辟。
4.在3中开辟了内存之后,通过调用free函数,将该结点加入到空间链表中,并继续执行malloc函数中的遍历空间链表,获得该新加入的结点,返回给用户。
接下来,我将我实现的malloc的整体代码贴出来,然后,将我对教程中有看法的地方提出来,进行推敲和探讨。(其中,为了检测程序的运行状况,我按照教程后面的教学内容,写了一个简单的打印函数,用来查看程序调用过程中,空间链表的变化,对程序进行了一个简单的测试)
extern _heapstart;
#define NULL 0
#define E 1
/*定义空间链表的结点控制信息*/
union Node{
struct {
union Node *next;
unsigned size;
} s;
long a;
};
typedef union Node Header;
static Header * startp; /*起始结点位置*/
static Header * prevp; /*当前结点的前一个结点位置*/
static Header * p; /*当前结点位置*/
/*用到的函数原型的定义*/
void * malloc(int); /*申请内存*/
Header * morecore(unsigned int); /*从未映射堆中申请空间*/
void free(void *); /*释放空间(添加到空间链表中)*/
int getListSize(); /*得到现在空间链表的大小(自我测试用)*/
void myPrintf(char *,...); /*简单的打印函数*/
void * malloc( int byteSize){
/*将申请的字节数大小byteSize转换成头结点大小为单位的内存块的个数*/
int nunits = (byteSize + sizeof(Header)-1) / sizeof(Header);
if(startp == NULL){ /*当头结点为空的时候,进行初始化*/
startp = (Header *)(_heapstart - 16); /**/
startp->s.size = 0; /*循环链表的头结点的本体信息空间大小为0*/
startp->s.next = startp;
}
for(p = startp->s.next , prevp = startp ; ; prevp = p , p = p->s.next){
if(p->s.size >= nunits){
if(p->s.size <= nunits + E){
prevp->s.next = p->s.next;
}else{
p->s.size -= nunits + 1;
p += p->s.size + 1;
p->s.size = nunits;
}
startp = prevp;
return (void *)(p+1);
}
/*链表中不存在合适的结点,去为开辟空间申请*/
if(p == startp){
if((p=morecore(nunits)) == NULL){
return NULL;
}
p = startp;/*在后面实现的free函数中,要对startp进行设置,使继续进行的遍历能定位到新添加的结点*/
}
}
}
Header * morecore(unsigned int num){
unsigned int total ;
unsigned int former;
unsigned int temp;
Header * up; /*申请到的空间地址*/
total = (num+1)*(sizeof(Header));
former = _heapstart;
temp = _heapstart + total; /*空间申请后的位置*/
if(temp < 65535){ /*判断是否超出为映射堆空间的最大范围*/
up = (Header *)former;
up->s.size = num;
_heapstart = temp; /*设置_heapstart的值*/
free((void *)(up+1));/*加入到空闲堆空间链表中*/
return up; /*返回整个结点的头指针*/
}else{
return NULL;
}
}
/*释放一个空间*/
void free(void * free){
Header * freep = ((Header *)free)-1; /*指向结点控制信息*/
for(p = startp->s.next , prevp = startp; ;prevp = p , p = p->s.next){/*遍历链表寻找插入结点的位置*/
if(freep > p && freep < p->s.next){ /*freep在两个结点之间*/
/*将连续的空间连到一起*/
if(freep == p + p->s.size + 1){ /*p与freep连成新的p*/
p->s.size += freep->s.size + 1;
if(p + p->s.size + 1 == p->s.next){ /*新p与p->s.next连成新p*/
p->s.size += (p->s.next)->s.size + 1;
p->s.next = (p->s.next)->s.next; /*设置p指向p->s.next的下一个结点*/
}
startp = prevp; /*设置新的startp,使malloc中的下次遍历能够直接定位到这个新申请的结点*/
}else if(freep + freep->s.size + 1 == p->s.next){ /*只将freep与p->s.next连接成新的freep*/
freep->s.size += (p->s.next)->s.size +1;
freep->s.next = (p->s.next)->s.next;
p->s.next = freep;
startp = p;/*设置新的startp,使malloc中的下次遍历能够直接定位到这个新申请的结点*/
}else{ /*不能连接就直接加入到链表*/
freep->s.next = p->s.next;
p->s.next = freep;
startp = p;/*设置新的startp,使malloc中的下次遍历能够直接定位到这个新申请的结点*/
}
break;
}
if(p >= p->s.next && freep > p){/*找到位置,freep放在链表的末尾*/
if(p + p->s.size + 1 == freep){ /*将链表末尾的结点P与freep连接*/
p->s.size += freep->s.size + 1;
startp = prevp;/*设置新的startp,使malloc中的下次遍历能够直接定位到这个新申请的结点*/
}else{
freep->s.next = p->s.next;
p->s.next = freep;
startp = p;/*设置新的startp,使malloc中的下次遍历能够直接定位到这个新申请的结点*/
}
break;
}
}
}
int getListSize(){
int num = 1;
Header * tempStartp = startp;
Header * tempP ;
if(tempStartp == NULL) return 0;
myPrintf("[%d]NodeSize:%d;",0,tempStartp->s.size); /*打印起始结点的大小*/
for(tempP = tempStartp->s.next; ;tempP = tempP->s.next){
if( tempP == tempStartp){
break;
}
myPrintf("[%d]NodeSize:%d;",num,tempP->s.size); /*打印其他结点的大小*/
num ++;
}
return num;
}
/*简单的打印函数,功能还不是很完善^_^*/
void myPrintf(char * former,...){
char *p;
int intValue;
char *cp;
int offsetnum;
char cValue;
char temp;
int t1;
int iv;
int itger;
offsetnum = 6;
for(p = former;*p;p++){
if(*p != '%'){
temp = *p;
asm mov dl,temp
asm mov ah,2
asm int 21h
continue;
}
++p;
if(*p =='c'){
cValue = *(int *)(_BP+offsetnum);
offsetnum+=2;
asm mov dl,(cValue)
asm mov ah,2
asm int 21h
}else if(*p =='d'){
intValue = *(int *)(_BP+offsetnum);
offsetnum +=2;
itger = 1000;
for(t1 = 0;t1<4;t1++){
iv = intValue/itger;
intValue = intValue-iv*itger;
itger = itger/10;
asm mov dl,(iv)
asm add dl,30h
asm mov ah,2
asm int 21h
}
asm mov dl,(intValue)
asm mov ah,2
asm int 21h
}else if( * p =='s'){
for( cp = (char *)*(int *)(_BP+offsetnum) ;*cp;cp++){
temp = *cp;
asm mov dl,temp
asm mov ah,2
asm int 21h
}
offsetnum+=2;
}else{
temp = *p;
asm mov dl,temp
asm mov ah,2
asm int 21h
}
}
}
/*简单的测试*/
main(){
int b;
void * a;
void * c;
void * c1;
b = getListSize();
myPrintf("ListSize:%d/",b);
a = malloc(5);
free(a);
b = getListSize();
myPrintf("ListSize:%d/",b);
a = malloc(1);
b = getListSize();
myPrintf("ListSize:%d/",b);
c1 = malloc(2);
free(a);
b = getListSize();
myPrintf("ListSize:%d/",b);
c = malloc(9);
free(c);
b = getListSize();
myPrintf("ListSize:%d/",b);
free(c1);
b = getListSize();
myPrintf("ListSize:%d/",b);
}
然后,就来说一下,我对教程中参考代码的一些看法。我们根据开始说的malloc引发的整个内存申请流程,我们发现,当我们从未映射堆中申请到合适的内存块后,我们需要用free函数将该内存块加入到空间链表中。这里就很关键了,首先,教程中没有提供我们free函数实现的参考代码,所以,教程代码的整体性我们没法猜测。不过,我们可以看到,在我们将内存块加入到空间链表时,有一个对内存块的连接操作的判断(目的应该是教程中提到的防止“内存碎片”的产生吧)。free掉的内存块和它前后的内存块存在以下几种连接可能:
1.与前后内存块都不相邻。
2.仅与前面的内存块相邻。
3.仅与后面的内存块相邻。
4.既与前面的内存块相邻,又与后面的内存块相邻。
假设free函数中当前遍历到的结点为fp,那么,经过上面四种可能的连接方式,我们可以很清楚的看到,我们加入到空间链表的内存块要么成为了当前结点fp(其实是和fp构成了新的内存块,但是,头结点还是fp,所以,新结点我们依然可以说是fp,只是size大小发生了变化),要么就成为了fp->s.next(即当前结点fp的下一个结点)。新结点加入到空间链表之后,malloc应该继续遍历,得到该新添加的结点并返回给申请用户。最高效率的处理方式,当然是在free添加结点后,malloc下一次定位直接定位到该节点。所以,我们需要在free函数的处理过程中做一些处理,并在malloc函数中也做相应的处理,以达到这个目的。怎么做呢?从malloc中可以知道,下一次循环,p = p->s.next,也就是说,我们在所有的处理之后,malloc中的p应该定位到free函数添加的结点的前一个结点位置,才能保证malloc接下来的循环定位第一次就定位在空间链表中新添加的结点处。这个过程,相当于我们将p的起始点重新给设置了一下,为了程序上的统一,我们用startp来个给p设置新值。这样,我们必须在free函数中,根据不同的情况,重新将startp的值进行改变,使得startp指向新结点的前一个结点:
(1)当新结点与前后内存块都不相邻 startp = p
(2)当新结点仅与前面的内存块相邻 startp = prevp(prevp是p的前一个结点)
(3)当新结点仅与后面的内存块相邻 startp = p
(4)当新结点既与前面的内存块相邻,又与后面的内存块相邻 startp = prevp
free函数中我们对startp做了处理,使得新的startp指向了新添加结点的前一个结点,那么,我们在malloc中就应该使用startp来重新设置控制循环遍历的当前结点p的值,即p = startp,这样p就指向了新添加的结点的前一个位置,下一次遍历就能直接定位到这个新添加的结点了,就能以最快的效率将该新结点内存返回给申请的用户了。
我说到的对教程中参考代码的看法就是malloc中在调用morecore函数申请到内存后的处理,本人认为教程中的代码
if(p == startp){
if((p=morecore(nunits)) == NULL){
return NULL;
}
}
应该加入之前说的那条 p = startp 代码,即
if(p == startp){
if((p=morecore(nunits)) == NULL){
return NULL;
}
p = startp;
}
这样就能和后面的free实现过程相呼应,使得整体逻辑就顺畅了。(另外,本人认为(p=morecore(nunits))==Null这行代码可以简化成morecore(nunits)==NULL,反正我们后面也没使用这里的p做什么操作。)
【结论】教程能把知识点描述那么清晰,可见汇编网技术专家确实不是盖的!但是,上面提到的代码处确实应该有一个对于循环控制变量p的定位处理的,想必是专家也有打瞌睡的时候吧。(*^__^*)...嘻嘻
- [qiansanshi] 熟悉的ID,熟悉的事,祝朋友们学习工作愉快 11/04 19:22
- [mywiil] 这里曾经给我们带来了那么多回忆,却不曾想,慢慢的被我们遗忘。 没事的时候,回来看看吧,这里有我 08/31 09:41
- [rotapple] 知道了,这是书后面的实验章节。我还没看到那边 08/29 16:14
- [rotapple] 这是什么书? 08/29 14:54
- [tomato] 怎么都这么伤感! 08/29 09:12
- [tomato] 怎么都这么伤寒! 08/29 09:12
- [rotapple] 感觉只要理解了跳转的过程及ip修改的方式。就不难理解了。 08/16 15:00
- [游客] add al,80h CF=1;OF=1;SF=0;ZF=1;PF=1 你 07/13 16:47
- [游客] 谢谢 很有用 06/23 18:06
- [游客] 你向下跳转的例子显然不符合题意,用7ch向下跳转那就相当与jmp指令的效果了(没有循环),要知道lo 03/26 20:51
- [sgiceleo] 谢谢一直关注我的作业 ,虽说有很多很多不懂的 ,但是看到那么多编程前辈们的鼓励 ,我有信心继续努力! 02/15 10:02
- [oldmtn] 我好久没上了,看到了你的留言. 讨教你一下,你想过深入学习汇编没有,现在搞汇编人很少啊 大多数人 09/28 14:36
- [ym3823078] 来 看看,呵呵 07/22 00:31
- [netbox] 请教一个问题:8根数据总线一次可以传送一个8位二进制数据(即一个字节)。 不是一个数字占一个字 06/23 19:57
- [netbox] 呵呵,感谢你~~光临我的博客!多多指导,。。加油! 06/23 19:50
- [游客] 说汇编难学,我不信。别人说的不算,我得试验一下。 ----------------- 说的好! 03/07 19:38
[ 272003327 发表于 2009-02-16 16:02 ]
分析得精彩!!!!!!!!!!
我也正在学习C语言
[ mywiil 发表于 2009-02-19 10:11 ]
看出前段时间我学习的不够细致了。
看了楼主的描述,整个思路清晰很多了。
谢谢。
[ maxm 发表于 2009-02-19 23:57 ]
楼主学完汇编用了多长时间?
[ 游客 发表于 2009-02-20 09:40 ]
去看看他的博客中的学习作业的提交时间就知道了。
[ 游客 发表于 2009-03-10 08:47 ]
喔喔 现在也是在学习这一块了,过来学习学习,顺便给LZ顶一下。。。
[ 游客 发表于 2011-06-23 18:06 ]
谢谢 很有用