搜索
bottom↓
回复: 67

[古董贴][交流]ICC动态内存分配ABC

[复制链接]

出0入296汤圆

发表于 2006-6-15 10:48:12 | 显示全部楼层 |阅读模式
学过C语言的人都知道,标准的c语法中提供一种动态分配内存的方法。这种方法的核心主要就是如下的几个函数

    int sizeof(void *)   void *malloc(size_t size)  void *realloc(void *ptr, size_t size) void free(void *ptr)

    通过这些函数,我们可以来构建链表以及其他所谓“高级”的数据结构。众所周知,这些函数的声明包含在 stdlib.h(Standard Library 标准库)中。然而并非众所周知的就是,在单片机的使用中,不同的编译器采用了不同的方法来处理用户的这一“合理要求”。

    以ICC为例,很多人可能会被如下的代码困扰——即便他是直接从那些AVR C技术手册上直接“抄”下来的。



# include <stdlib.h>



struct Chain

{

   char Data;

   struct Chain *Next;

};



void main(void)

{

   char  n = 0;

   struct Chain *Head = NULL;

   struct Chain *p = NULL;

   

    Head = (struct Chain*)malloc(sizeof(struct Chain));

    p = Head;

    for (n = 0;n<80;n++)

    {

        p->Next = (struct Chain*)malloc(sizeof(struct Chain));

        p->Next->Data = n;

        p->Next->Next = NULL;

        p = p->Next;

    }

   

    while(1);

}



    这段代码从c语法的角度来说绝对的“Standard(符合标准)”,但是ICC环境下无论用单片机还是AVR Studio跟踪都会发现这样一个事实……分配空间总是失败——申请控件返回的总是NULL(0)。

    难道ICC欺骗我们?至少“清华大学出版社 沈文 Eagle lee 詹卫前 编著的 《AVR单片机 C语言开发入门指导》”这本书中就只字未提为何总是分配失败的问题。



    问题何在呢?——正像本论坛常见的一种情形一样:

    遇到有人发帖“[求救]为什么我的XX总是调不通”,以HJJAVR为首的一批高手便会在给你回答问题以前提醒一句“注意看Datasheet(Datasheet)”。

    于是,我们打开ICC的Help topic,选中Standard Library And Memory Allocation Functions这一项,发现以下的内容:



……

void free(void *ptr)



frees a previously allocated heap memory.

释放先前分配的堆栈内存。



……

void *malloc(size_t size)



allocates a memory chunk of size "size" from the heap. It returns 0 if it cannot honor the request.

从堆栈中分配大小为size的连续内存空间。如果系统不能满足这个要求,它将返回0。



void _NewHeap(void *start, void *end)



initializes the heap for memory allocation routines. Malloc and related routines manage memory in the heap region. See Data Memory Usage for information on memory layout. A typical call uses the address of the symbol _bss_end+1 as the "start" value. _bss_end defines the end of the data memory used by the compiler for global variables and strings. You add 1 to it since the stack checking functions uses the byte at _bss_end to store a sentinel byte. The "end" value must not run into the stacks.



extern char _bss_end;



_NewHeap(&_bss_end+1, &_bss_end + 201); // 200 bytes heap



Beware that for a microcontroller with a small amount of data memory, it is often not feasible or wise to use dynamic allocation due to its overhead and potential for memory fragmentation. Often a simple statically allocated array serves ones needs better.



……



void *realloc(void *ptr, size_t size)



reallocates a previously allocated memory chunk with a new size.

重新分配先前分配过的存储器空间。



……



注意中间,中间的一个函数

void _NewHeap(void *start, void *end)

他并非是Stdlib中的“常任理事”,从他的声明中可以看出,他可能是一个关键人物。抛开中间艰涩难懂的E文(对我来说是的),我们注意到以下的代码片断



extern char _bss_end;



_NewHeap(&_bss_end+1, &_bss_end + 201); // 200 bytes heap



首先它声明了一个外部的变量 _bss_end,这显然是前面某一个存储区域的结尾地址,下面函数的调用方法证明了这一猜测,它通过改变量的地址偏移了200个字节分配了200个字节大小的堆栈。



ok ,let`s try it!



修改代码如下:

# include <stdlib.h>



struct Chain

{

   char Data;

   struct Chain *Next;

};

extern char _bss_end;





void main(void)

{

   char  n = 0;

   struct Chain *Head = NULL;

   struct Chain *p = NULL;

    _NewHeap(&_bss_end+1, &_bss_end + 201);

   

    Head = (struct Chain*)malloc(sizeof(struct Chain));

    p = Head;

    for (n = 0;n<80;n++)

    {

        p->Next = (struct Chain*)malloc(sizeof(struct Chain));

        p->Next->Data = n;

        p->Next->Next = NULL;

        p = p->Next;

    }

   

    while(1);

}



AVR Studio调试通过!哈哈,成功了。



(未完待续)
-----此内容被Gorgon Meducer于2006-06-15,10:50:02编辑过

出0入296汤圆

 楼主| 发表于 2006-6-15 11:07:47 | 显示全部楼层
以上的文字好好比是电影前的引子,下面的内容才是前言。

   我不知道各位看官当中有多少人和我一样才刚刚觉得对“C指针”有了一点感觉。所谓“南顿北渐”,我是多次看到了“(void *)”这样的指针才顿悟的。C语言指针真的太灵活,要想安全的使用他的确不容易,因为指针的本质不过是一个地址,好比禅学中“万物都有是佛,然而佛具体是什么样子却是人心强加上去的”论调一样,地址无处不在,C语言中一切都可以化为地址,具体地址是什么类型,却是人们自己规定的。(void *)说明了C语言世界的本质。

    以上的一番梦话本没有什么逻辑,最多只能算是玄幻罢了,如果能惊醒梦中人,我当然很高兴,如果你还在“火神求火”,那就随我来慢慢渐悟吧。



    以后的章节中,我会先简单说明一下指针的种类,提一下垃圾收集的概念,然后通过仿真一个MFC中的消息队列的例子说明什么叫做面向对象——其实C语言也可以做到的。

    指针无所不在:

    感觉到他的存在,使我们感叹C语言的多彩;

    掌握它的存才,使我们觉得花花世界不过表象,本质不过是空(空指针(void*));

    忘记他的存在,真的就是宇宙境界了……


-----此内容被Gorgon Meducer于2006-06-15,11:09:21编辑过

出0入296汤圆

 楼主| 发表于 2006-6-15 11:44:52 | 显示全部楼层
第一章 地址就是地址  指针就是指针

    地址就是地址,指针就是指针;

    地址是本质,指针是一种特殊的数据类型。用手指月亮,我们顺着手的方向可以看到月亮,但是手却不是月亮;这就好比是地址和指针的关系,用指针可以获得它指向的地址,但是指针却不是地址。

    那么指针是什么呢?指针是一个特殊的无符号整形数据,他的长度等于所用处理器的地址线的宽度,该数据类型通过一个专用的操作符&来获得别人的地址数据,当通过&符号获得一个地址以后,指针就是一个普通的无符号整形变量,你可以对它进行任何整形数据可以进行的操作,因为二者本质是相同的,只不过编译器没有给普通的整形数据化为白天鹅的能力,即普通变量不能通过操作符*来获得指针所指向的数据。当然强制转换例外。我们不是常常看到  #define PINB        (*(volatile unsigned char *)0x36) 这样的强制转换么?实际上你可以把0x36换成任何无符号整形变量(乱来出事情我可不管)。

    所以,指针 = 整形变量 + 特权   ,他的本质就是一个长度为地址线宽度的无符号整形变量,通过强制转换任何普通整形变量也可以摇身一变,成为指针。

    地址是本质,是目的;指针是幻象,是空(void *);



    了解了本质,我们再来看表象——指针的类型是怎么回事?

    (未完待续)

出0入0汤圆

发表于 2006-6-15 16:54:56 | 显示全部楼层
Gorgon Meducer 每次都是经典!

出0入0汤圆

发表于 2006-6-15 17:33:10 | 显示全部楼层
有空再来看,下班先!



出0入0汤圆

发表于 2006-6-15 17:48:52 | 显示全部楼层
good!

出0入0汤圆

发表于 2006-6-15 19:36:43 | 显示全部楼层
经典就是经典!我顶!

楼主带给我的除了望眼欲穿的渴盼还有长夜的难眠!

出0入296汤圆

 楼主| 发表于 2006-6-15 21:04:05 | 显示全部楼层
为了说明地址 指针 数据类型之间的关系,我们要借用一个小小的工具sizeof(),值得补充说明的是,sizeof()是一个系统运算符,而不是一个库函数,他能真实地反映出给定对象所占用的内存空间数目。

    首先,我们来看看以下的运算:

    char a[]={1,2,3,4};

    char *b = a;



    那么

    sizeof(a) = ?

    sizeof(b) = ?

    sizeof((*b)) = ?

    sizeof(a[0]) = ?



    首先我们来说前两个题目,容易知道a是一个数组,那么sizeof求得就是数组的大小,结果是4;b是一个指针型变量,前面说过,他的本质就是一个无符号型的整数,由于AVR内部采用的是16地址线,所以,结果是2;

    接着我们来看第三题,结果是多少呢?4还是2?

    第四题毫无悬念,结果应该是1。



    通过以上的题目,我想大部分人和我一样的人都只能回答正确三道题(算我小人度君子腹好了),错误的题目正像你们猜得那样,是第三题,然而,第三题却只是一个前台的小丑,真正的幕后BOSS却是第一题。



    大家有没有思考过这样一个问题:字符串数组,我们使用'\0'表示结尾,通过判断'\0'的位置我们可以获得字符串的有效长度,那么sizeof()是通过什么标志来获得一个普通的数值数组的大小的呢?我们学C语言的时候,老师不说数组名可以近似看作一个指针么?——问题就出在这个近似上了。



    首先,在强调一个概念,指针就是指针,地址就是地址。指针既然有变量也就有指针常量(volatile unsigned char *)0x36是常量指针,数组也是一种常量指针。



    其次,说明一个概念,指针实际上是由两个部分组成,我们不妨用一个结构体来说明它的本质(实际情况不同编译器是不一样的)。



    struct Point

    {

        WORD Addr;          //一个记录地址的整型,WORD就是该处理器的字

        long ChunkLen;       //该指针指向空间的长度,由指针声明符来赋值

    };

    &操作实际是给Addr赋值

    int *b中int实际是告诉Chunklen = 2;

    容易理解

    int **b就是声明一个上面的结构体,Addr是某一个指针变量的“如上的结构体的”地址,Chunklen是“如上的结构体“的,Addr的的长度。



    我们用这个结构来描述一下上面的题目(伪代码)

    第一题:

    const struct Point a = {\

        Addr = "系统分配的一个地址空间的起始地址";

        Chunklen = 4; // 这个数据是通过声明时候给他赋值获得的

        }+“后面的数据”//注意数组声明的时候,数据和以上的结构体是一个整体



     第二题:

     struct Point b = {

      Addr = a.Addr;

      Chunklen = 2;   //a.Addr的长度就是2

     }

     第三题:

     sizeof((*b)) 表示 求b指向的数据元素的数据类型的长度,所以结果是1;



     第四题

     实际上是第三题的另外一种表示方式。



     然而,从第三题的意图来看,他是想声明一个指向数组的指针而不是一个指向数组第一个元素的指针,那我们来看看谭浩强书上怎么写的:

     (*b)[4] = &a; 大家注意,这里居然非要说明所指向的数组的元素个数,并且还要给已经是指针常量的a取地址?呵呵,看了上面的结构体你还不明白么?a平时实际上就是一个数据类型,它是有地址的,构造一个指向数组的指针实际上是构造一个指向该结构体指针,该指针的Addr通过&获得a结构体的地址,通过申明数组后面数据的长度获得整个数组结构的长度,实际上可以表述为

    b.Addr = (WORD)&a;

    b.Chunk = 4 + sizeof(POINT);

     ^_^万般皆空,各种不同的类型不过十表象。我上面提到的结构体只是一种便于理解指针的模拟,实际情况并不是这样,众位看官拍砖的时候也要看清方向哈。



    留一个思考题:

void main(void)

{

    char a[] = {1,2,3,4};

    void (*b)(void);

    SystemInit();

    b = main;

   

    T = sizeof((*b));

    while(True)                                             //Super Lood

    {

    }

}

T结果是多少?不准编译哈——提示下sizeof是一个对数据类型和结构取大小的操作符。

-----此内容被Gorgon Meducer于2006-06-15,21:09:38编辑过


-----此内容被Gorgon Meducer于2006-06-15,21:24:54编辑过

出0入0汤圆

发表于 2006-6-16 08:55:04 | 显示全部楼层
仰慕ING!

出0入0汤圆

发表于 2006-6-16 09:11:59 | 显示全部楼层
我觉得楼主应该著书了,很多经典帖子呀。

出0入0汤圆

发表于 2006-6-16 09:39:25 | 显示全部楼层
T==2?

出0入296汤圆

 楼主| 发表于 2006-6-16 10:29:10 | 显示全部楼层
如果T = sizeof(b),那么结果是2。

出0入0汤圆

发表于 2006-6-16 14:52:04 | 显示全部楼层
景仰~~~~~

出0入0汤圆

发表于 2006-6-16 17:56:21 | 显示全部楼层
楼主,如果T = sizeof(b),那么结果是2。因为您告诉我B是16位的指针,清楚了,谢谢!

*b是MAIN,大小我还是不知道,我考了个大鸭蛋,真对不起老师。

出0入296汤圆

 楼主| 发表于 2006-6-16 21:48:19 | 显示全部楼层
呵呵,main是函数的地址,他不是一个数据类型,所以根本无法编译通过的。

^_^

哈哈,上当了吧。注意题目的提示。地址就是地址,指针就是指针。

出0入0汤圆

发表于 2006-6-16 22:34:45 | 显示全部楼层
学好数据结构这本书..这个就不是难题了...

这个是最基本的.



建议用标准C自己写,,这样子不受编译器的影响

...目前我不喜欢用编译器中的任一个库...除非float点.




-----此内容被boy123于2006-06-16,22:35:15编辑过

出0入296汤圆

 楼主| 发表于 2006-6-17 10:39:07 | 显示全部楼层
同意楼上意见,我就尝试着写了很多库,可是在网上测试的时候,多半被排砖……

5555555555555555555555

出0入0汤圆

发表于 2006-6-17 11:42:55 | 显示全部楼层
老师,我就特喜欢你写的库函数,我都搜索出来收藏了,呵呵,菜鸟穷没法给您版税。



我记得C里有指向函数的函数指针,类似汇编调用函数的过程,B=MAIN是把函数入口地址给指针吧,我很苯,老师您打骂都可以,别抛下学生不管!

出0入296汤圆

 楼主| 发表于 2006-6-17 19:19:39 | 显示全部楼层
-------------------------------------------------------------------

to:楼上

一口一个老师的……我要折寿的……看清楚帖子,交流二字写得多大的……

下一个章节会提到函数指针的,敬请期待。最近正在写事件驱动系统,不好意思,更新会慢一点。

先奉上一个事件驱动的主干雏形,大家有空的话劳烦慢慢看哈。



小弟又要献丑了……



#ifndef _USE_MSG_H_

#define _USE_MSG_H_

/***********************************************************

*  函数库说明:消息队列系统函数库                          *

*  版本:      v1.12                                       *

*  作者:      傻孩子                                      *

*  创建日期:  2006年5月24日                               *

* -------------------------------------------------------- *

*  [支持库]                                                *

*  支持库名称:RD_MacroAndConst.h                          *

*  需要版本:  v0.01 &abv                                  *

*  函数库说明:系统常用宏定义库                            *

*                                                          *

*  支持库名称:stdlib.h                                    *

*  需要版本:  6.31A                                       *

*  函数库说明:系统标准函数库                              *

*                                                          *

*  支持库名称:RD_UseBITs.h                                *

*  需要版本:  v1.00 &abv                                  *

*  函数库说明:系统位段操作函数库                          *

* -------------------------------------------------------- *

*  [版本更新]                                              *

*  修改:      傻孩子                                      *

*  修改日期:  2006年5月25日                               *

*  版本:      v1.10                                       *

*                                                          *

*  修改:      傻孩子                                      *

*  修改日期:  2006年5月30日                               *

*  版本:      v1.11                                       *

*                                                          *

*  修改:      傻孩子                                      *

*  修改日期:  2006年6月15日                               *

*  版本:      v1.12                                       *

* -------------------------------------------------------- *

*  [版本历史]                                              *

*       v1.00  提供了消息循环队列的基本结构体声明和队列操  *

*              作函数。拥有一个基本的消息队列配送函数。    *

*              提供基本的消息创建、删除和复制函数。自动消  *

*              息垃圾收集。初步建立了一个消息函数地图。    *

*       v1.10  建立的简单的进程处理构架。                  *

*       v1.11  增加了对消息类型鉴别的说明。增加了消息广播  *

*              功能。修改了消息的结构体,增加了消息的类型  *

*              鉴别字节。                                  *

*        v1.12 将所有char型数据更新为unsigned char。       *

*              修改了进程处理函数的调度模式。 修正了指针   *

*              传递的错误。                                *

* -------------------------------------------------------- *

*  [使用说明]                                              *

*           1、在系统初始化的时候调用函数MSGInit()来初始   *

*              化消息对系统;同时通过定义宏                *

*              MSG_REGISTER_PROC_FUNCTION来说明静态进程函  *

*              数的消息处理函数和动作函数(使用函数指针)。  *

*              每增加一个静态进程,要更新一次进程计数器    *

*              MSG_Proc_Counter,保证每一个进程都能被有效  *

*              的寻址。                                    *

*           2、通过宏 INSERT_EVENT_PROCCESS_LOOP_CODE()    *

*              来指定默认的消息处理函数,并且通过插入该宏  *

*              到系统主循环中来保证整个消息处理系统的正常  *

*              运行。也可以使用宏                          *

*              INSERT_EVENT_PROCCESS_LOOP_DEFAULT_CODE来设 *

*              定默认的消息处理函数。                      *

*           3、用户通过每一个进程的消息处理函数来处理消息  *

*              和动作函数之间的通讯,用户接收的消息无需自  *

*              行销毁,系统会统一的进行垃圾收集。如果用户  *

*              需要转发消息给别的进程,需要调用本库提供的  *

*              消息复制函数copyMSG来复制消息,并通过addMSG *

*              函数来添加消息到消息队列中。特别需要注意的  *

*              是,为了保证整个消息队列的实效性,在消息处  *

*              理函数中不要出现过长时间复杂度的代码。      *

*           4、系统为每一个消息都提供了一个消息类型的鉴别  *

*              标志,用户在发送消息的时候需要认真填写,这  *

*              些鉴别标志包括标志消息为错误信息;标志消息  *

*              为指令、数据;标志消息为广播等等。其中,广  *

*              播消息会自动发给所有的有效进程。错误的消息  *

*              类型将会导致不可预计的系统错误。            *

*           5、用户创建消息的时候,需要用专门的函数creatMSG*

*              并传递消息的正确描述。这些描述包括消息的发  *

*              送者函数指针,消息接收进程的消息处理函数指  *

*              针,消息的类型,消息的长度,和消息字节串。  *

*           6、用户可以通过宏MSG_QUEUE_SIZE来定义消息队列  *

*              的大小,值得注意的是,这个队列只是一个指针  *

*              队列,并不会占用过多的系统资源。            *

*           7、用户可以通过宏MSG_PROCESS_COUNT来定义最大的 *

*              进程PCB数量。这个数值直接影响系统允许的静态 *

*              进程的数量,并要占用一定的空间。这个数目的  *

*              最小值是1,因为默认有一个系统默认消息处理   *

*              进程。                                      *

*           8、用户需要MSG_REGISTER_MSG_FUNCTION宏来说明   *

*              系统进程的初始化函数,通过这个初始化函数告  *

*              诉消息系统,一共有多少静态进程,并且说明这  *

*              些静态进程的相关函数指针是什么。            *

***********************************************************/

# include <RD_MacroAndConst.h>

# include <stdlib.h>

# include <RD_UseBITs.h>

/********************

*   系 统 宏 定 义  *

********************/



/*------------------*

*   常 数 宏 定 义  *

*------------------*/

#ifndef MSG_QUEUE_SIZE

    # define MSG_QUEUE_SIZE    50

#endif

#ifndef MSG_PROCESS_COUNT

    # define MSG_PROCESS_COUNT 8      

#endif

#ifndef MSG_REGISTER_MSG_FUNCTION

    # define MSG_REGISTER_MSG_FUNCTION

#endif

# define NULLPROC    NULLProc



/*------------------*

*   动 作 宏 定 义  *

*------------------*/

# define INSERT_EVENT_PROCCESS_LOOP_CODE(DEFAULT_FUNCTION_NAME) processMSG(DEFAULT_FUNCTION_NAME);

# define INSERT_EVENT_PROCCESS_LOOP_DEFAULT_CODE                processMSG(SYS_DEFAULT);



# define MSG_FLAG(Flag)        SET_BIT8_FORMAT(Flag)



# define MSG_FLAG_ERROR_BIT       BIT0

# define MSG_FLAG_DATA_BIT        BIT1

# define MSG_FLAG_CMD_BIT         BIT2

# define MSG_FLAG_BROADCAST_BIT   BIT3

# define MSG_FLAG_TEST_BIT        BIT4

//# define MSG_FLAG_Reserved    BIT5

//# define MSG_FLAG_Reserved    BIT6

//# define MSG_FLAG_Reserved    BIT7



# define MSG_FLAG_ERROR           0

# define MSG_FLAG_DATA            1

# define MSG_FLAG_CMD             2

# define MSG_FLAG_BROADCAST       3

# define MSG_FLAG_TEST            4



#ifndef MSG_REGISTER_PROC_FUNCTION

    # define MSG_REGISTER_PROC_FUNCTION

#endif



# define SEND_MESSAGE(From,To,Flag,MsgLen,MSG)    addMSG(creatMSG(From,To,Flag,MsgLen,MSG))



/********************

*    结构体宏定义   *

********************/

typedef struct Message

{

    BOOL (*From)(volatile struct Message *MSG);                         //如果处理了信息,返回True,否则是False

                BOOL (*To)(volatile struct Message *MSG);

               

    unsigned char MsgLen;

                unsigned char Flag;                                     //消息类型鉴别字节



                unsigned char *Msg;                                     //消息指针

}MESSAGE;





typedef struct Process

{

    BOOL (*ProcIO)(volatile MESSAGE *MSG);

                void (*Proc)(void);

                BOOL IfProcAlive;

}PROCESS;



/********************

*   全局变量声明区  *

********************/

volatile MESSAGE *MSG_Queue[MSG_QUEUE_SIZE];



volatile PROCESS ProcPCB[MSG_PROCESS_COUNT+1];



unsigned char MSG_Head = 0;

unsigned char MSG_Tail = 0;

unsigned char MSG_Counter = 0;

unsigned char MSG_Proc_Counter = 1;                         //进程计数器



/********************

*   函 数 声 明 区  *

********************/

BOOL addMSG(volatile MESSAGE *MSG);

BOOL getMSG(volatile MESSAGE **MSG);

void processMSG(BOOL (*Default)(volatile MESSAGE *MSG));

BOOL SYS_DEFAULT(volatile MESSAGE *MSG);

volatile MESSAGE *copyMSG(volatile MESSAGE *MSG);

void delMSG(volatile MESSAGE *MSG);

void MSGInit(void);

void NULLProc(void);

volatile MESSAGE *creatMSG(BOOL (*From)(volatile MESSAGE *MSG),BOOL (*To)(volatile MESSAGE *MSG),

                  unsigned char Flag,unsigned char MsgLen,unsigned char *Msg);



/***********************************************************

*  函数说明:空函数                                        *

***********************************************************/

void NULLProc(void)

{



}

                                          

/***********************************************************

*  函数说明:消息队列系统初始化函数                        *

* -------------------------------------------------------- *

*  [注意事项]                                              *

*         1、ProcPCB[]是一个函数指针数组,里面登记着       *

*            所有系统合法的消息处理函数的地址。            *

*         2、ProcPCB[]下标中,0为系统保留,其余为用户      *

*            自定义。                                      *

*         3、用户需要自己通过编写函数并通过连接宏          *

*            MSG_REGISTER_PROC_FUNCTION来注册合法消息处理  *

*            函数。                                        *

*         4、编程的时候,每增加一个静态的进程,就要增加一  *

*            次进程计数器MSG_Proc_Counter。                *

***********************************************************/

void MSGInit(void)

{

    unsigned char n = 0;

               

    for (n = 0;n<MSG_PROCESS_COUNT+1;n++)

    {

        ProcPCB[n].ProcIO = SYS_DEFAULT;

        ProcPCB[n].Proc   = NULLPROC;

        ProcPCB[n].IfProcAlive = False;

    }

               

    MSG_REGISTER_PROC_FUNCTION

}



/***********************************************************

*  函数说明:消息创建函数                                  *

*  输入:    消息信息                                      *

*  输出:    消息指针                                      *

***********************************************************/

volatile MESSAGE *creatMSG(BOOL (*From)(volatile MESSAGE *MSG),BOOL (*To)(volatile MESSAGE *MSG),

                  unsigned char Flag,unsigned char MsgLen,unsigned char *Msg)

{

    volatile MESSAGE *TempMSG = (volatile MESSAGE *)malloc(sizeof(volatile MESSAGE));



    TempMSG->From = From;

    TempMSG->To = To;

    TempMSG->Flag = Flag;

    TempMSG->MsgLen = MsgLen;

    TempMSG->Msg = Msg;



    return TempMSG;

}



/***********************************************************

*  函数说明:消息删除指令                                  *

*  输入:    需要释放消息的指针                            *

***********************************************************/

void delMSG(volatile MESSAGE *MSG)

{

    if (MSG != NULL)

    {

        free(MSG->Msg);

        free((void *)MSG);                                  //释放空间

    }

}



/***********************************************************

*  函数说明:消息复制函数                                  *

*  输入:    源消息指针                                    *

*  输出:    目标消息指针                                  *

***********************************************************/

volatile MESSAGE *copyMSG(volatile MESSAGE *MSG)

{

    unsigned char n = 0;

    unsigned char Len = MSG->MsgLen;

   

    volatile MESSAGE *TempMSG =(volatile MESSAGE *)malloc(sizeof(volatile MESSAGE));

    unsigned char *TempMsgInfo = (unsigned char *)malloc(Len);

   

    TempMSG->From = MSG->From;

    TempMSG->To = MSG->To;

    TempMSG->Flag = MSG->Flag;

    TempMSG->Msg = TempMsgInfo;

    TempMSG->MsgLen = MSG->MsgLen;

    for (n = 0;n < Len;n++)                                 //消息复制循环

    {

        TempMsgInfo[n] = MSG->Msg[n];

    }

               

    return TempMSG;

}



/***********************************************************

*  函数说明:系统默认消息处理函数                          *

*  输入:    消息指针                                      *

***********************************************************/

BOOL SYS_DEFAULT(volatile MESSAGE *MSG)

{

    MSG = MSG;

    return True;

}



/***********************************************************

*  函数说明:消息调度函数                                  *

*  输入:    消息处理函数指针                              *

* -------------------------------------------------------- *

*  [注意事项]                                              *

*         1、允许自定义默认的消息处理程序。                *

*         2、每一个有效的消息在传递给相应函数后,即便传递  *

*            成功,也会在返回调用以后被立即销毁,所以如果  *

*            需要使用该消息中的内容进行再次消息传递,需要  *

*            对消息进行复制,复制以后再进行传递,否则容易  *

*            造成野指针现象。                              *

*         3、用户在整个过程中,无须自行删除消息。          *

***********************************************************/

void processMSG(BOOL (*Default)(volatile MESSAGE *MSG))

{

    volatile MESSAGE *msg = NULL;

    static unsigned char n = 0;



   

    if (getMSG(&msg))                                       //没有消息

    {

      

        if (MSG_FLAG((msg->Flag)).MSG_FLAG_BROADCAST_BIT)   //检测广播标志

        {

            for (n = 0;n<MSG_Proc_Counter;n++)

            {

                (*ProcPCB[n].ProcIO)(msg);                  //发送广播信息

            }

        }

        else

        {

        

            if (!(*(msg->To))(msg))                         //向指定函数发送消息

            {

                (*Default)(msg);                            //如果消息没有得到处理

            }

         

        }

                       

        delMSG(msg);                                        //消息处理完后立即释放

    }

   

    if (ProcPCB[n].IfProcAlive)                             //处理进程

    {

        (*ProcPCB[n].Proc)();

    }

   

    n ++;

    if (n >= MSG_Proc_Counter)

    {

        n = 0;

    }



}



/***********************************************************

*  函数说明:添加消息到队列                                *

*  输入:    消息指针                                      *

*  输出:    添加是否成功                                  *

***********************************************************/

BOOL addMSG(volatile MESSAGE *MSG)

{

    if ((MSG_Head == MSG_Tail) && (MSG_Counter >= MSG_QUEUE_SIZE))

    {

        return False;                                       //队列已满,添加失败

    }



    MSG_Queue[MSG_Tail] = MSG;

    MSG_Tail++;

    MSG_Counter++;

    if (MSG_Tail >= MSG_QUEUE_SIZE)

    {

        MSG_Tail = 0;

    }

               

    return True;

}



/***********************************************************

*  函数说明:获取消息函数                                  *

*  输入:    消息指针                                      *

*  输出:    操作是否成功                                  *

***********************************************************/

BOOL getMSG(volatile MESSAGE **MSG)

{

    if ((MSG_Head == MSG_Tail) && (MSG_Counter == NULL))

    {

        return False;                          //消息队列为空

    }

     

     

    (*MSG) = MSG_Queue[MSG_Head];

    MSG_Head++;

    MSG_Counter--;

   

    if (MSG_Head >= MSG_QUEUE_SIZE)

    {

        MSG_Head = 0;

    }

               

    return True;

}

#endif


-----此内容被Gorgon Meducer于2006-06-17,19:26:31编辑过

出0入0汤圆

发表于 2006-6-18 21:01:28 | 显示全部楼层
老师,我想和您交流啊,可我的水平太差了,你的事件驱动我都看不懂,还在努力消化中,没法给您提好的建议,感觉对不起您啊,总是索取却不能帮您点什么,惭愧啊。

出0入0汤圆

发表于 2006-6-20 16:33:27 | 显示全部楼层
有空就继续函数指针,期待中...

出0入0汤圆

发表于 2006-6-22 09:28:02 | 显示全部楼层
我不顶你还要谁顶你啊,哈哈

出0入0汤圆

发表于 2006-6-23 18:27:35 | 显示全部楼层
继续啊,老大最近很忙吗?

出0入0汤圆

发表于 2006-6-28 07:58:10 | 显示全部楼层
lz快来继续啊!

出0入0汤圆

发表于 2006-6-28 09:08:10 | 显示全部楼层
Gorgon Meducer 大虾出手 ,



必属精品 !!



顶你没商量 !!!

出0入4汤圆

发表于 2006-6-28 09:55:34 | 显示全部楼层
看来我还很菜,我都看不懂了

出0入0汤圆

发表于 2006-6-28 21:57:10 | 显示全部楼层
看那一遍 ,不知道 void (*Proc)(void); 有何用途





以前也见过人家做过 ,类似的消息或者过程驱动的东西 ,



感觉复杂的东西还是上操作系统好 ,简单的东西 ,



用这种写法 ,又觉得有点把简单问题搞复杂的做法 .



应该是我见识短浅吧 ,还是请Gorgon Meducer 大虾 ,



弄几个具体例子 ,来说说这种结构 ,框架的优势吧 .

出0入296汤圆

 楼主| 发表于 2006-7-1 10:41:22 | 显示全部楼层
对不起大家,最近一直在尝试使用事件驱动来实现一个嵌入式系统框架,目的是,利用该框架实现嵌入式系统开发的彻底模块化,最终可以通过上位机方便的制作一个类似Vissual Basic的可视化嵌入式开发系统。

我使用事件驱动,实际上是尝试实现一个小的操作系统调度。从processMSG函数可以看出,被叫得消息处理函数才会得到执行,通过该函数其下的实际任务处理部分得以打开或者关闭,注意到processMSG通过宏定义实际上是在主程序的while超级循环中调用的,所以,这种结构实际上是一种简易的事件驱动操作系统——当然,对于任务的编写有严格的要求,由于没有抢占式的任务调度机制(只能靠任务本身自动释放系统资源),所以规定在编写任务的时候,任务的事件复杂度不宜过于复杂,最好不要出现内部的死循环。——这个系统对大家来说也许意义不大,但是对于我来说,他却能方便地快速完成一个机器人智能控制系统的定制(配合上位机),同时,利用函数指针和消息地图的方法,可以很方便的实现一个功能强大的机器人智能系统。

后面,我们将重点谈一谈函数指针。
-----此内容被Gorgon Meducer于2006-07-01,10:42:36编辑过

出0入296汤圆

 楼主| 发表于 2006-7-1 11:06:13 | 显示全部楼层
第二章 函数指针

    关于函数指针,我们常常可以在网上看到如下的典型例子:

//example of function

#include <iostream.h>

#include <math.h>

void integral(double(*f)(double),double,

double,double);

double f(double x);

void main()

{

double a,b,delta;

cout<<"Input boundary and accuracy:”

<<endl;

cin>>a>>b>>delta;

integral(f,a,b,delta);

}

double f(double x)

{

return exp(x)/(1+x*x);

}



void integral(double(*fn)(double),double a,

double b,double epsilon)

{

int m;

double I0,I1,I2,Im,h,delta;

h=b-a;

I0=fn(a)+fn(b);

I1=I0*h/2; I2=0;

while(fabs(I1-I2)>epsilon){

I2=I1; h=h/2; Im=0;

for(m=1;m<((int)(b-a)/h);m++)

Im=Im+2*f(a+m*h);

I1=(I0+Im)*h/2;

}

cout<<"the integral is :"<<I1;

}

    今天,我就不在凑这个热闹了,网上关于类似代码的分析很多,但是今天我要很大胆很肯定地告诉大家,这种使用方法是很基本很皮毛的,函数指针更高的一种理解是大家现在很流行的一种概念——暂且不说——如果你能理解以下的内容的话,说明你对这种概念已经有了超过我的理解了。

    C语言,不同于C++,是一种面向过程的编程语言。他支持结构体,动态内存分配和函数指针。这就是基本材料。

    假设一种情形,我们建立一个结构体:

typedef struct Message

{

    BOOL (*From)(volatile struct Message *MSG);                         //如果处理了信息,返回True,否则是False

    BOOL (*To)(volatile struct Message *MSG);

      

    unsigned char MsgLen;

    unsigned char Flag;                                     //消息类型鉴别字节



    unsigned char *Msg;                                     //消息指针

}MESSAGE;



    From是一个函数指针,表示消息发送者;To是一个函数指针,表示消息的接受者;

    Flag是消息的类型标志,我们暂且忽略;

    MsgLen和Msg就是一个字节串,表示要传送的内容。



    在外部,我们可以直接通过(*From)(MSG)来访问发送者,也可以通过(*To)(MSG)来访问接受者。

    这种结构是什么呢?将要处理的数据和处理它的内容封装在一起,这就是什么?这就是面向对象。

出0入296汤圆

 楼主| 发表于 2006-7-1 11:23:41 | 显示全部楼层
换一种更实际的情形,假设我们需要开发一种产品,这种产品有很多基本的功能,通过各个基本功能的不同组合方式,就可以实现不同的新的复杂功能,我们假设,提供这种自定义的组合的权利给用户是我们产品的亮点,那么如何实现这一目的呢?自然,函数指针就是不二的选择。

   

    单独的函数指针变量其实意义不大,只有形成了规模,形成了数组,才能给他一片用武天地。那么函数指针的定义形式有几种呢?



    1、不带返回值和参数的函数指针声明

    void (*Example)(void)

    2、带返回值的函数指针声明

    int (*Example)(void)

    3、带参数地函数指针声明

    void (*Example)(int a)

    4、带函数指针作为参数的函数指针声明

    void (*ExampleA)(void (*ExampleB)(void))

    5、函数指针数组

    void (*Example[10])(void);

    6、函数指针常量(数组)

    void (*const Example[10])(void);   通过这个可以节省宝贵的SRAM,把指针变量放到flash里面存储。

出0入0汤圆

发表于 2006-7-4 10:51:08 | 显示全部楼层
Gorgon Meducer 大虾 ,好象是逢周六 ,更新一次



好慢

出0入0汤圆

发表于 2006-7-4 11:31:22 | 显示全部楼层
期待看到函数指针数组怎么灵活应用的。

出0入0汤圆

发表于 2006-7-4 12:53:31 | 显示全部楼层
打击一下,

只要了解了库的实现方法,用库函数还是很好的,至少很多算法库的实现比自己做的要好一些。

出0入0汤圆

发表于 2006-7-4 14:43:53 | 显示全部楼层
什么呀!什么呀!又是C,又是ASM的.怎么一点也不懂,我只会汉语,不知道可以吗?

将来偶请人开发个软件,只要你将你的逻辑说明白了,功能就会实现!什么程序,代码,全都回家去!  哈哈!!

出0入0汤圆

发表于 2006-7-9 14:45:18 | 显示全部楼层
【32楼】 mored

贡献点自己做的好库啊。

LZ继续啊,出点题目也不错啊。

出0入296汤圆

 楼主| 发表于 2006-7-15 14:59:34 | 显示全部楼层
函数指针的应用。

   

    函数指针在小型潜入式系统中有很多很灵活的应用,其中一些甚至是和设备和人身安全息息相关的。设想一种大型车床设备,这种大型设备往往存在不同的状态,这些状态下关机或者复位的方式是不同的,所以必须要能够根据当前的状态选择正确的复位程序,这样才能保证人员和财产的安全。从潜入式系统的设计角度来说通过一个函数指针,从函数数组中选择合适的复位程序,是一种非常有效的解决方案。



    void Reset_StateA(void);

    void Reset_StateB(void);

    void Reset_StateC(void);



    ……

    void (*Reset[])(void) = {Reset_StateA,Reset_StateB,Reset_StateC};

    ……

    Reset[NowState]();               //根据状态选择复位函数;



    第二种应用在桌面系统中非常常见,这就是消息地图的实现。

    考虑一个结构体将消息和处理该消息的函数封装在一起,通过这样一个结构体数组,很难容易就实现了消息地图:

typedef struct MsgMap

{

    unsigned char MSGInfo;

    BOOL (*MSGFunc)(volatile MESSAGE *MSG);

}MSGMAP;



MSGMAP  ProcMsgMap[] = {

                        {MSG_A,FuncA},

                        {MSG_B,FuncB},

                        {MSG_C,FuncC},

                        };



……



BOOL MsgProccess(volatile MESSAGE *MSG)

{

char n = 0;

     

     if ((!(MSG_FLAG(MSG->Flag).MSG_FLAG_CMD_BIT))

        && (!(MSG_FLAG(MSG->Flag).MSG_FLAG_ERROR_BIT)))

     {

         return False;

     }

     

     for (n = 0;n<UBOUND(ProcMsgMap);n++)

     {

         if ((MSG->Msg[0]) == ProcMsgMap[n].MSGInfo)

         {

             RM_SendMessageTo = MSG->From;

             if ((*(ProcMsgMap[n].MSGFunc))(MSG))

             {

                 return True;

             }

         }

     }

     return False;        

}



……



以上只是抛砖引玉,实际上,在操作系统中,函数指针是系统调度的基本要素,如果没有函数指针,将无法完成系统的任何一种任务调度行为。
-----此内容被Gorgon Meducer于2006-07-15,15:01:19编辑过

出0入296汤圆

 楼主| 发表于 2006-7-15 15:11:23 | 显示全部楼层
第三章 动态内存分配的原理和库函数代码解析(Stdlib.h)



     动态内存分配做一个ANSI_C中一个便准功能存在于函数库Stdlib.h中,动态内存分配实际上就是通常意义的堆(Heap)的分配,堆的概念有一点类似栈,相关知识,大家可以去查阅数据结构的相关章节,我这里就不再掉书袋了。



     ICC中,关于动态内存分配有如下的一些宏定义,我们可以从这里面先窥探一下,这种动态内存分配到底可能是一个怎样的结构:

_Alloc.h



typedef struct cell_hdr

        {

        struct cell_hdr *next;

        void *EndAddr;

        int size;

#ifdef DEBUG

        unsigned int InUse;

#endif

        } CELL_HDR;



extern CELL_HDR *__FreeList;



#define _BND        1



#define NEW_SIZE(s, e)                (((char *)e) - (((char *)s) + sizeof (CELL_HDR)))

#define REAL_SIZE(siz)                ((siz) + sizeof (CELL_HDR))

#define INCR_SIZE(s, siz)        ((char *)(s) + REAL_SIZE(siz))

#define DELTA                        (sizeof (CELL_HDR) + 8)



#define GET_HDR(p)                 (CELL_HDR *)((char *)p - sizeof (CELL_HDR))



我们注意到,这里面存在一个似乎是关键要素的结构体和一些宏定义,同时还有一个被称之为__FreeList的结构体指针。观察结构体和该指针:

typedef struct cell_hdr

        {

        struct cell_hdr *next;     //单链表指针

        void *EndAddr;             //似乎是内存块(chunk)的结束地址

        int size;                  //内存块的大小

#ifdef DEBUG

        unsigned int InUse;        //测试用的一个标志变量

#endif

        } CELL_HDR;



extern CELL_HDR *__FreeList;        

很显然,我们可以猜测到,作者是要使用一个结构体来描述内存分配的结构,这就有点类似文件结构有他的FAT表一样,但是,由于没有看到内存块的起始地址描述项,我们猜测,这些描述内存块的结构体变量应该不是集中放在一起形成一张表的,很可能是直接和分配出去的存储空间打包在一起的。

出0入296汤圆

 楼主| 发表于 2006-7-15 15:22:07 | 显示全部楼层
我们来分析西面的宏定义:



#define _BND   1

这是什么?不知道,暂时放在一边。



#define NEW_SIZE(s, e)      (((char *)e) - (((char *)s) + sizeof (CELL_HDR)))

这个被称作New size的宏定义有两个参数,s(start)和e(end),从后面描述的内容来看,是返回一个起始地址和终止地址之间一共包含了多大内存的一个数值——当然,还要加上一个和该空间打包在一起的描述结构体变量的大小。It seems easy!



#define REAL_SIZE(siz)      ((siz) + sizeof (CELL_HDR))

Real Size?看来是要返回申请分配siz大小的空间时,实际占用的内存大小的(要加上一个额外的描述结构体变量嘛!)



#define INCR_SIZE(s, siz)   ((char *)(s) + REAL_SIZE(siz))

Increase Size 地址增量,从后面描述的内容来看,返回的是一个从地址s开始分配了需要的地址空间和额外结构开销以后,地址的偏移量。



#define DELTA         (sizeof (CELL_HDR) + 8)

Delta?就是希腊字母戴尔塔,意为小增量,哈哈,鬼知道是干什么的,但是从宏定义来看是包含了一个最小地址分配单位——一个描述结构体加上8个字节……也许作者想要规定一个最小单位吧,不然就不划算了(当然不划算,结构体变量被分配的空间还大……)



#define GET_HDR(p)       (CELL_HDR *)((char *)p - sizeof (CELL_HDR))

get HDR?什么意思?但是从后面的宏定义可以推测出,我们给他的动态地址,他根据这个地址获得结构体变量,那么HDR就表示该空间的结构体描述变量的地址了哈!



take it easy.

(未完待续……)

出0入0汤圆

发表于 2006-7-17 16:20:44 | 显示全部楼层
再顶 ,确实好文 ,



期待早日更新

出0入0汤圆

发表于 2006-7-20 11:14:40 | 显示全部楼层
这么好的帖子,怎么不是酷帖

出0入0汤圆

发表于 2006-7-20 17:09:31 | 显示全部楼层
复位函数指针感觉不错,要找机会试一下。

操作系统内存分配对我来说太复杂了,楼主玩大了!

出0入296汤圆

 楼主| 发表于 2006-7-22 20:33:27 | 显示全部楼层
ICCAVR 的标准库函数中,关于动态内存分配的文件一共有四个,他们分别是:

    _ALLOC.H  ALLOC.C  freelist.c newheap.c

    不知道大家还记得本贴一开始的那段文字中,关于ICC中动态内存分配失败的原因是我们忘记进行初始化——即调用函数_NewHeap(),看来这个函数相当关键,我们就从newheap.c开始,一点一点剖析整个代码。

    打开文件newheap.c,看到以下内容:



#include <_alloc.h>

void _NewHeap(void *start, void *end)

        {

        CELL_HDR *new = start;



        new->next = __FreeList;

        __FreeList = new;



        new->size = NEW_SIZE(start, end);

        new->EndAddr = end;

#ifdef DEBUG

        new->InUse = 0;

#endif

        }

抛开DEBUG宏的部分不看,代码的主体是这样的:

void _NewHeap(void *start, void *end)    //这个函数显然是用来加入新的堆栈空间的,其中start和end是新空间的起始和终止地址。

{

        CELL_HDR *new = start;          //定义了一个内存描述结构体指针,并且这个指针指向新空间的起始地址



        new->next = __FreeList;         //假设__FreeList是一个指向可用空间的指针,那么显然,作者意图是在该空间之前添加新加入的内存空间的         

        __FreeList = new;               //更新了__Freelist指针,验证了上面的假设



        new->size = NEW_SIZE(start, end);//获取该空间的大小

        new->EndAddr = end;              //描述新空间的结束,没什么好多说的。

}



通过这段代码,我们可以获得以下信息:

1、首先,整个动态内存分配的基本单位是我们前面提到过的  结构体+内存空间 这样的一种结构,即便是空闲的空间也是如此,可以推测,后面的malloc应该是进一步把某一些符合要求的大德这样的结构进行瓜分吧。

2、FreeList顾名思义,就是指向空闲可用空间的指针,不过作者喜欢在曾经空闲空间的前面添加新空间——这也是因为没有尾指针造成的必然吧。

3、明显,不通过该函数添加(初始化)可以用的内存空间,是没有办法进行动态内存分配的——而且,ICC的这种动态内存分配方法相当浪费……


-----此内容被Gorgon Meducer于2006-07-22,20:34:42编辑过

出0入296汤圆

 楼主| 发表于 2006-7-22 20:46:37 | 显示全部楼层
看了上面的内容,下面我们直接打开ALLOC.C,单刀直入,看看alloc函数

void *malloc(size_t size)

        {

        CELL_HDR **qb;

        CELL_HDR *p, *q;

        char *cp, *rp;

        int i;



        size = (size + _BND) & ~_BND;



        for (qb = &__FreeList; p = *qb; qb = &(*qb)->next)

                if (size <= p->size)

                        {

                        if (DELTA < p->size - size)

                                {

                                                         

                                q = (CELL_HDR *)INCR_SIZE(p, size);

                                           q->next = p->next;

                                q->size = p->size - REAL_SIZE(size);

                                q->size &= ~_BND;

                                q->EndAddr = p->EndAddr;

#ifdef DEBUG

                                q->InUse = 0;

#endif

                                p->EndAddr = q;

                                p->size = NEW_SIZE(p, p->EndAddr);

                                *qb = q;

                                }

                        else

                                *qb = p->next;

                        p->next = 0;

#ifdef DEBUG

                        p->InUse = 1;

#endif

                        cp = INCR_SIZE(p, 0);                                                   return cp;

                        }

        return 0;

        }



    说句实话,阅读这段代码实在费了我不少时间,主要是作者变量命名实在不好捉摸,在完全没有弄懂作者意图的时候,尤其具有迷惑性,所以,下面我特别将这些变量的意义说明一下:

CELL_HDR **qb;    一个指向指针的指针(废话!),是作者进行链表遍历的一个关键中间变量,作者通过for([1],[2],[3])结构中[1]对该变量进行初始化,通过[2],[3]部分进行更新,说它是一个中间变量,是因为,他并不直接参与后面的操作,而是将它指向的指针赋值给后面的一个变量p,来完成关键性的操作。

CELL_HDR *p, *q;  两个指针变量,指向存储空间描述结构体,其中,p的值为链表当前访问的空闲空间的地址,而q则是一个关键的中间变量,这个变量是进行空闲空间“瓜分”以后,用来存储新的空闲空间的地址的;而p在瓜分以后,保存的地址虽然没有变化,但是里面的内容被进行了更新,这个时候p指向的结构体描述的就不是空闲空间,而是分配出去的空间了。

char *cp, *rp;    cp纯属过渡指针,没有什么多说的,置于rp嘛……暂时没有找到在什么地方用的……

int i;            …… -_-b   


-----此内容被Gorgon Meducer于2006-07-22,20:52:59编辑过

出0入296汤圆

 楼主| 发表于 2006-7-22 21:08:17 | 显示全部楼层
下面,我们就来解释一下什么是“瓜分”。我们假设有一块空闲空间(下面的图示表示)

    [描述结构体A][-----------------------------------------------------------]

    我们对它进行瓜分——为什么对它进行瓜分,因为他刚好合适……(为什么合适?因为作者喜欢这样……这么假设把,他是第一个合适的,不管后面有没有更合适的,作者就简单得选它了,置于会不会造成浪费,作者没有考虑……)



    首先这样:

    [描述结构体A][---------------------][描述结构体B][-----------------------]

   

    然后,修改描述结构体A的相关内容,比方说size、EndAddr之类的

    最后,把空闲指针指向描述结构体B——这样才能保证下一次结构体B能被用到啊;返回描述结构体A后面实际内存空间的地址——大功告成。不是很复杂吧?



    看看代码是怎么做的。

   

    size = (size + _BND) & ~_BND;    //size是我们希望申请分配的空间大小,这里我们要对它进行地址对齐——对齐到WORD(unsigned int型)



    for (qb = &__FreeList; p = *qb; qb = &(*qb)->next)  //如前面所说,进行遍历

        

        if (size <= p->size)    //判断某一个空闲块大小是否合适

        {

            if (DELTA < p->size - size)  //判断,如果进行瓜分,剩下的空间是否大于最小内存分配单元的空间大小——前面章节里面有提到过哦

           {

             /* some space left, link it to the free list*/

             /* 后面就是瓜分的代码 */

                                 

                                q = (CELL_HDR *)INCR_SIZE(p, size);         //一个存储空间加一个空间描述头部 Addr + HeadStructure + RamSpace

                                q->next = p->next;

                                q->size = p->size - REAL_SIZE(size);

                                q->size &= ~_BND;

                                q->EndAddr = p->EndAddr;

#ifdef DEBUG

                                q->InUse = 0;

#endif

                                p->EndAddr = q;

                                p->size = NEW_SIZE(p, p->EndAddr);

                                *qb = q;

                                }

                        else

                                *qb = p->next;   //如果不合适进行瓜分,那么就直接把该空前全部分配出去算了……

                        p->next = 0;  //嫁出去的女儿,泼出去的水——和母体断绝联系,切断链表

#ifdef DEBUG

                        p->InUse = 1;

#endif

                        cp = INCR_SIZE(p, 0);                           //跳过空间的头部,给定实际的存储空间地址

                        return cp;  

                        }

        return 0;

        }



很简单哈?

知道以上的结构后面关于free函数的阅读就不难了哦。

打个比方,free就是合并,把我们要释放的空间删除掉他自己的空间描述结构体,然后合并到前或后的空间里面去就好了……天下分久必合嘛……

自己阅读了哦,我就不婆婆妈妈了。



static void Merge(CELL_HDR *cell)

        {

        CELL_HDR **qb;

        CELL_HDR *p;



        for (qb = &__FreeList; p = *qb; qb = &(*qb)->next)

                /* merge with next chunk

                 */

                if (cell->EndAddr == p)

                        {

                        *qb = p->next;

                        cell->size += REAL_SIZE(p->size);  /* Added by wph */

                        cell->EndAddr = p->EndAddr;

                        Merge(cell);

                        break;

                        }

                /* merge with previous chunk

                 */

                else if (p->EndAddr == cell)

                        {

                        *qb = p->next;

                        p->size += REAL_SIZE(cell->size);  /* Added by wph */

                        p->EndAddr = cell->EndAddr;

                        Merge(p);

                        break;

                        }

        if (!p)

                {

                cell->next = __FreeList;

                __FreeList = cell;

                }

        }



void free(void *p)

        {

        CELL_HDR *cell;



        if (!p)

                return;

        cell = GET_HDR(p);



        if ((char *)cell->EndAddr < INCR_SIZE(cell, cell->size)

#ifdef DEBUG

                || !cell->InUse

#endif

                )

                {

#ifdef DEBUG

                fprintf(stderr, "bad memory %x
", p);

#endif

                return;

                }



#ifdef DEBUG

        cell->InUse = 0;

#endif

        Merge(cell);

        }



   
-----此内容被Gorgon Meducer于2006-07-22,21:09:39编辑过

出0入0汤圆

发表于 2006-8-30 12:14:37 | 显示全部楼层
佩服!

出0入0汤圆

发表于 2007-11-24 16:58:04 | 显示全部楼层
学习

出0入0汤圆

发表于 2008-1-15 18:44:19 | 显示全部楼层
佩服!
也解决了今天遇到了链表的问题

出0入0汤圆

发表于 2008-1-16 03:31:14 | 显示全部楼层
收藏

出0入0汤圆

发表于 2008-1-16 08:42:01 | 显示全部楼层
这么经典的帖子竟然才看到。。

出0入0汤圆

发表于 2008-1-16 08:55:54 | 显示全部楼层
强人,顶!

出0入0汤圆

发表于 2008-4-2 14:31:19 | 显示全部楼层
刚发现,佩服,mark

出0入0汤圆

发表于 2008-4-16 20:43:46 | 显示全部楼层
我喜欢

出0入0汤圆

发表于 2008-4-18 09:59:40 | 显示全部楼层
通过阅读这一段代码发现一个隐式的BUG
  
#include <_alloc.h>
void _NewHeap(void *start, void *end)
        {
        CELL_HDR *new = start;

        new->next = __FreeList;
        __FreeList = new;

        new->size = NEW_SIZE(start, end);
        new->EndAddr = end;
#ifdef DEBUG
        new->InUse = 0;
#endif
        }

void *malloc(size_t size)
        {
        CELL_HDR **qb;
        CELL_HDR *p, *q;
        char *cp, *rp;
        int i;

        size = (size + _BND) & ~_BND;

        for (qb = &__FreeList; p = *qb; qb = &(*qb)->next)
                if (size <= p->size)
                        {
                        if (DELTA < p->size - size)
                                {
                                                         
                                q = (CELL_HDR *)INCR_SIZE(p, size);
                                           q->next = p->next;
                                q->size = p->size - REAL_SIZE(size);
                                q->size &= ~_BND;
                                q->EndAddr = p->EndAddr;
#ifdef DEBUG
                                q->InUse = 0;
#endif
                                p->EndAddr = q;
                                p->size = NEW_SIZE(p, p->EndAddr);
                                *qb = q;
                                }
                        else
                                *qb = p->next;
                        p->next = 0;
#ifdef DEBUG
                        p->InUse = 1;
#endif
                        cp = INCR_SIZE(p, 0);                                                   return cp;
                        }
        return 0;
        }




首先我分析本质上MCU的内存布局为下面所示
-------------------------------------- 低地址区
|                                    |
|                                    |
|         全局和静态变量空间         |   
|                                    |
|                                    |
--------------------------------------
|  ICCAV标志变量 _bss_end            |
———————————————————
|                                    |
|                                    |
|            未分配空间区            |
|                                    |
———————————————————    <-SP 堆栈顶指针  高地址区


隐藏的BUG就是:
  如果_NewHeap申请的全局堆空间过大,则可用内存块就可以延伸到堆栈区,但如果MALLOC空间较小,系统可能会正常工作,因为在堆中使用实际空间小,但是如果MALLOC申请空间大,使用就会发生一个问题,会写高地址堆栈区,很危险。

出0入296汤圆

 楼主| 发表于 2008-4-21 21:33:29 | 显示全部楼层
to 【52楼】 zjybest 绝对痴迷
    你说的情况是存在的。ICC在帮助中,曾经警告过使用者,慎重。我们可以通过ICC提供的Memory Map File,来预先反先这种BUG。

出0入0汤圆

发表于 2009-4-13 09:26:32 | 显示全部楼层
mark

出0入0汤圆

发表于 2009-6-21 18:20:40 | 显示全部楼层
好文!

出0入0汤圆

发表于 2010-7-26 23:30:25 | 显示全部楼层
回复【42楼】Gorgon Meducer 傻孩子
    看了上面的内容,下面我们直接打开alloc.c,单刀直入,看看alloc函数
void *malloc(size_t size)
{
cell_hdr **qb;
cell_hdr *p, *q;
char *cp, *rp;
int i;
size = (size + _bnd) &amp; ~_bnd;
for (qb = &amp;__freelist; p = *qb; qb = &amp;(*qb)-&gt;next)
if (size &lt;= p-&gt;size)
{
if (delta &lt; p-&gt;size - size)
{
  
q = (cell_hdr *)incr_size(p, size);
                                           q-&gt;next = p-&gt......
-----------------------------------------------------------------------


真不好意思挖一下哈,最近需要看看动态内存分配的问题,十分感谢lz这种交流的精神,感觉写的即透彻又详细,关键还是四年前的文章,lz这种乐于开源的精神居然在四年前就开始了真是太佩服了。十分感谢。

麻烦问几个问题:

1)cell_hdr **qb为什么要定义成 **的模式,直接cell_hdr *qb不是就可以了么,还剩的以后又*又&的。比如
for (qb = &__FreeList; p = *qb; qb = &(*qb)->next) 不就直接成了for (qb = __FreeList; p = qb; qb = qb->next) 这样怎么不行了。
2) for (qb = &__FreeList; p = *qb; qb = &(*qb)->next) 我怎么看不出来这个for循环的退出条件是什么呀。 p = *qb这个负值什么时候等于0呀?(C语言没学好,呵呵)

感谢各位高手指点。

aaa1982

出0入296汤圆

 楼主| 发表于 2010-7-27 10:07:59 | 显示全部楼层
to 【56楼】 aaa1982
     用指向指针的指针(也就是cell_hdr **qb)是一个技巧,因为作者用了单链表来维护堆内存单元。
这种情况下,对于单链表的操作,如果我们使用普通的指针来访问,搜索元素肯定没有问题,但是如果
要删除元素就头大了,因为删除一个元素所要做的就是:A.通过搜索找到这个元素;B.修改该元素的上
一个元素Next指针。问题就出在,如果我们用普通指针,虽然能完成搜索操作,但是一旦我们找到目标
元素,就无法回到上一个元素(Next指针永远指向后续元素)。用指向指针的指针则可以解决这个问题,
因为他本身始终指向pNext指针,当我们(*qb)时,表示的就是Next指针本身——它既可以表示所指向的
单元地址,也可以表示Next指针自己。也就是说,当我们通过*qb搜索到指定元素以后,直接操作*qb就
可以完成元素的删除。

出0入0汤圆

发表于 2010-7-27 11:44:50 | 显示全部楼层
不知道哪位把这古董帖顶了出来,发现傻孩子写得挺好的,

出0入0汤圆

发表于 2010-7-27 11:45:20 | 显示全部楼层
感谢:能帮忙回答一下for循环怎么退出的问题么?

出0入0汤圆

发表于 2010-9-28 19:37:25 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-9-28 19:48:09 | 显示全部楼层
最近在用IAR的时候也要用到动态内存分配,多读几遍。。

出0入0汤圆

发表于 2010-10-2 12:47:25 | 显示全部楼层
非常感谢先,因为最近自己做一个飞机游戏,用到动态分配,直接malloc就是NULL,看完您的文章,突然收获很多~~~

出0入0汤圆

发表于 2010-12-9 23:38:54 | 显示全部楼层
先标记着,慢慢消化!

出0入0汤圆

发表于 2011-5-8 15:36:02 | 显示全部楼层
这个cool帖才是真的酷!

出0入296汤圆

 楼主| 发表于 2011-5-9 11:58:02 | 显示全部楼层
to 【59楼】 aaa1982
    break??

出0入0汤圆

发表于 2011-10-31 15:24:28 | 显示全部楼层
MARK

出0入0汤圆

发表于 2011-10-31 16:55:30 | 显示全部楼层
Mark!
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-5-5 09:47

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表