单片机c语言内存管理_单片机c语言函数调用过程 指针 内存的分配 - CSDN
  • 单片机简单内存管理器 本代码基于无操作系统的STM32单片机开发,功能强大,可申请到地址空间连续的不同大小的内存空间,且用户接口简单,使用方便 memory.h #ifndef __MEMORY_H__ #define __MEMORY_H__ #include ...

    单片机简单内存管理器

    本代码基于无操作系统的STM32单片机开发,功能强大,可申请到地址空间连续的不同大小的内存空间,且用户接口简单,使用方便

    转载请注明出处:http://blog.csdn.net/u011833609/article/details/46834203

    memory.h

    #ifndef __MEMORY_H__
    #define __MEMORY_H__
    
    #include "stdio.h"
    #include "string.h"
    #include "includes.h"
    //用户使用
    typedef struct
    {
        void    *addr;      //申请到的内存的起始地址
        uint32_t size;      //申请到的内存的大小,按照块大小分配,大于等于申请大小
        uint16_t  tb;        //申请表序号,申请内存时分配,释放内存时使用,用户不使用
    }DMEM;
    
    //若返回空,则申请失败
    DMEM *DynMemGet(uint32_t size);
    
    void DynMemPut(DMEM *pDmem);
    
    
    #endif //__MEMORY_H__
    

    memory.c

    #include "memory.h"
    
    #define DMEM_BLOCK_SIZE         256      //内存块大小为128字节
    #define DMEM_BLOCK_NUM          20       //内存块个数为40个
    #define DMEM_TOTAL_SIZE         (DMEM_BLOCK_SIZE*DMEM_BLOCK_NUM)    //内存总大小
    
    typedef enum
    {
        DMEM_FREE   = 0,
        DMEM_USED   = 1,
    }DMEM_USED_ITEM;
    
    typedef struct
    {
        DMEM_USED_ITEM   used;       //使用状态
        uint16_t         blk_s;      //起始块序号
        uint16_t         blk_num;    //块个数
    }DMEM_APPLY;
    
    typedef struct
    {
        DMEM_USED_ITEM  tb_blk[DMEM_BLOCK_NUM];
        DMEM            tb_user[DMEM_BLOCK_NUM];        //用户申请内存信息
        DMEM_APPLY      tb_apply[DMEM_BLOCK_NUM];       //系统分配内存信息
        uint16_t        apply_num;      //内存申请表占用数目
        uint16_t        blk_num;        //内存块占用数目
    }DMEM_STATE;
    
    static uint8_t DMEMORY[DMEM_TOTAL_SIZE];
    static DMEM_STATE DMEMS = {0};
    DMEM *DynMemGet(uint32_t size)
    {
        uint16_t loop = 0;
        uint16_t find = 0;
        uint16_t blk_num_want = 0;
        DMEM * user = NULL;
        DMEM_APPLY *apply = NULL;
        
        //申请内存大小不能为0
        if(size == 0)               {   return NULL;    }
        //申请内存不可超过总内存大小
        if(size > DMEM_TOTAL_SIZE)  {   return NULL;    }
        //申请内存不可超过剩余内存大小
        if(size > (DMEM_BLOCK_NUM - DMEMS.blk_num) * DMEM_BLOCK_SIZE)   {   return NULL;    }
        //申请表必须有空余
        if(DMEMS.apply_num >= DMEM_BLOCK_NUM)   {   return NULL;    }
        
        //计算所需连续块的个数
        blk_num_want = (size + DMEM_BLOCK_SIZE - 1) / DMEM_BLOCK_SIZE;
        
        //寻找申请表
        for(loop = 0; loop < DMEM_BLOCK_NUM; loop++)
        {
            if(DMEMS.tb_apply[loop].used == DMEM_FREE)
            {
                apply = &DMEMS.tb_apply[loop];                  //申请表已找到
                user = &DMEMS.tb_user[loop];                    //用户表对应找到
                user->tb = loop;                                //申请表编号记录
                user->size = blk_num_want * DMEM_BLOCK_SIZE;    //分配大小计算
                break;
            }
        }
        
        //没有找到可用申请表,理论上是不会出现此现象的,申请表剩余已在上面校验
        if(loop == DMEM_BLOCK_NUM)  {   return NULL;    }
        
        //寻找连续内存块
        for(loop = 0; loop < DMEM_BLOCK_NUM; loop++)
        {
            if(DMEMS.tb_blk[loop] == DMEM_FREE)
            {//找到第一个空闲内存块
                for(find = 1; (find < blk_num_want) && (loop + find < DMEM_BLOCK_NUM); find ++)
                {//找到下一个空闲内存块
                    if(DMEMS.tb_blk[loop + find] != DMEM_FREE)
                    {//发现已使用内存块
                        break;
                    }
                }
                if(find >= blk_num_want)
                {//寻找到的空闲内存块数目已经够用
                    user->addr = DMEMORY + loop * DMEM_BLOCK_SIZE;  //计算申请到的内存的地址
                    apply->blk_s = loop;                            //记录申请到的内存块首序号
                    apply->blk_num = blk_num_want;                  //记录申请到的内存块数目
                    for(find = 0 ; find < apply->blk_num; find++)
                    {
                        DMEMS.tb_blk[loop + find] = DMEM_USED;
                    }
                    apply->used = DMEM_USED;                        //标记申请表已使用
                    DMEMS.apply_num += 1;
                    DMEMS.blk_num += blk_num_want;
                    
                    return user;
                }
                else
                {//寻找到的空闲内存块不够用,从下一个开始找
                    loop += find;
                }
            }
        }
        
        //搜索整个内存块,未找到大小适合的空间
        return NULL;
    }
    
    void DynMemPut(DMEM *user)
    {
        uint16_t loop = 0;
        //若参数为空,直接返回
        if(NULL == user)    {   return; }
        
        //释放内存空间
        for(loop = DMEMS.tb_apply[user->tb].blk_s; loop < DMEMS.tb_apply[user->tb].blk_s + DMEMS.tb_apply[user->tb].blk_num; loop++)
        {
            DMEMS.tb_blk[loop] = DMEM_FREE;
            DMEMS.blk_num -= 1;
        }
        //释放申请表
        DMEMS.tb_apply[user->tb].used = DMEM_FREE;
        DMEMS.apply_num -= 1;
    }


    展开全文
  • 在企业级项目开发中一个非常重要的设计就是如何有效...在C语言中,关于内存管理的知识点比较多,如函数变量、作用域、指针、堆 、栈、 常量区、全局静态区、要想真正掌握和理解C语言,就必须先精通C语言内存管理机制。
  • 单片机C语言实现数码管控制

    万次阅读 2017-06-13 10:20:20
    本文分析了数码管的工作原理,并尝试用C51单片机去控制数码管的显示。希望更加深入理解单片机的内部工作原理和C语言的特性。

    数码管作为人机接口的重要显示部件,广泛应用于各行各业。本文将围绕数码管的原理和使用展开讨论,实验内容也是由浅入深,结合C语言特性,着意于在实验中掌握数码管和C语言知识。文章采用与单片机C语言实现独立按键检测与矩阵键盘操作同样的开发板。以下先给出数码管的电路原理图。


    上图中的4个数码管为共阳极数码管。数码管实际是由8个发光二极管组成的,而共阳极是指将这8个二极管的正极连接到一个公共端。所以当这8个二极管的任何一个负极通低电平的时候,相应的二极管就会被点亮,相反,通高电平则灭。详细请参照以下原理图。


    图中任取一个数码管,有a, b, c, d, e, f, g, p分别代表组成该数码管的发光二极管。当要该数码管显示0到9之间任一数字的时候,要给每一个二极管不一样的电平(0或1),8个二极管分别由P0口的8个I/O口控制,P0口的控制输出即为段码。

    上面说的是单个数码管的显示,然后我们有四个数码管,怎么选择我要用哪个数码管呢?回到数码管原理图,可以看到数码管底下的DIG口分别连接到4个驱动三极管,并最终由P2口的4个端子来控制。这样就可以选择哪个数码管工作,譬如希望最左侧数码管亮,我们称其为千位数码管(因为有4个数码管,分别代表个十百千),则只需给P2.0口送低电平。

    实验一

    好了,现在就来做个实验,实验目标是使千位数码管显示数字6。直接上代码。

    #include "reg51.h"
     //char seg[10]={0xC0,0XF9,0XA4,0XB0,0X99,0X92,0X82,0XF8,0X80,0X90};
     code char seg[10]={0xC0,0XF9,0XA4,0XB0,0X99,0X92,0X82,0XF8,0X80,0X90};
     sbit QIAN = P2^0; //QIAN表示千位
     void main()
     {
     	P0 = seg[6];
    	QIAN = 0;
    	while(1)
    	{
    		
    	}
     }
    程序中,显示定义了段码,进入main函数后,先给P0口数字6的显示段码,再选通千位数码管,这样千位数码管就顺利地显示6。这里我们重点讲解seg数组。

    char seg[10]

    10:10个数连续存放。

    seg:代表了第一个数的首地址。

    char:每个数最大值不超过255,即一个内存单元(如果定义成int则每个数要占用两个内存单元)。

    但是这10个数存在哪呢?

    有两种方法。第一种也就是当我们采用char seg[...]={...}这种定义方法时,程序下载烧到ROM后,当下次上电时候,程序会自动将这数组拷贝到RAM。第二种当定义成code char seg[...]={...}时候,程序下载烧到ROM后,内核只从ROM中读取,并不通过RAM。这样的好处是节省了内存资源,但同时程序执行时seg数组也不能被更改。

    实验二

    接下来我们要使4个数码管都显示,本例显示1234。直接上代码:

    #include "reg51.h"
     //char seg[10]={0xC0,0XF9,0XA4,0XB0,0X99,0X92,0X82,0XF8,0X80,0X90};
     code char seg[10]={0xC0,0XF9,0XA4,0XB0,0X99,0X92,0X82,0XF8,0X80,0X90};
     sbit QIAN = P2^0;
     char i;
     char smgbuf[4]={1,2,3,4}; //从RAM的smgbuf这个地址开始连续存放4个数,并且每个数占一个单元。
    
     void delay(unsigned int x)
     {
     	while(x)   //注意这里不是1
    	{
    		x--;
    	}	
     }
    
     void load_smg()   //将数码管显示缓冲区的数据,显示到数码管上
     {
     	char i;
     	for(i=0;i<4;i++)	
    	{
    		P0=0xFF;   //消除上一个循环的影子,因为i每一次叠代时,数码管都会有上一次叠代的痕迹,0xFF则是使所有数码管灭掉。
    		P0 = seg[smgbuf[i]];
    		P2 = ~(1<<i);
    		delay(200);
    	}
     }
    
     void main()
     {
    	while(1)
    	{
    		load_smg();
    	}
     }
    实验三

    下面我们希望用四个数码管显示一个可变的数字,这个数字记录了程序执行进入main函数的次数。还是直接上代码(为了方便代码管理,拆分代码到main.c和smg.c):

    main.c文件

    #include "reg51.h"
    unsigned int count;
    extern void load_smg();
    
    void main()
     {
    	int a;
    	while(1)
    	{
    		load_smg();
    		a++;
    		if(a>=200)
    		{
    			count++;
    			a=0;			
    		}
    	}
     }
    smg.c文件

     #include "reg51.h" 
      //char seg[10]={0xC0,0XF9,0XA4,0XB0,0X99,0X92,0X82,0XF8,0X80,0X90};
     code char seg[10]={0xC0,0XF9,0XA4,0XB0,0X99,0X92,0X82,0XF8,0X80,0X90};
     char i;
     char smgbuf[4]={1,2,3,4}; //从RAM的smgbuf这个地址开始连续存放4个数,并且每个数占一个单元。
     sbit QIAN = P2^0;
     extern unsigned int count;	//外部申明
    
     void delay(unsigned int x)
     {
     	while(x)   
    	{
    		x--;
    	}	
     }
    
    void fill_smgbuf() //向LED缓冲区填充数据
    {
    	smgbuf[0]=count/1000;  //千位,我们希望千位数码管来显示数字的千位
    	smgbuf[1]=(count%1000)/100;  //百位
    	smgbuf[2]=((count%1000)%100)/10;   //十位
    	smgbuf[3]=((count%1000)%100)%10;   //个位
    }
    
    void load_smg()   //将缓冲区的数据,显示到数码管上
     {
     	char i;
    	fill_smgbuf();
     	for(i=0;i<4;i++)	
    	{
    		P0=0xFF;  
    		P0 = seg[smgbuf[i]];
    		P2 = ~(1<<i);
    		delay(200);
    	}
     }
    实验四
    接下来我们要引入独立按键控制数码管上的数字加1(K1键)或减1(K2键)。还是上代码:

    main.c文件

    #include "reg51.h"
    
    extern void load_smg();
    extern delay(unsigned int x);
    extern void key1();
    extern void key2();
    
    unsigned int count;
    
    void main()
     {
    	while(1)
    	{
    		load_smg();
    		key1();
    		key2();
    	}
     }
    smg.c文件

     #include "reg51.h" 
      //char seg[10]={0xC0,0XF9,0XA4,0XB0,0X99,0X92,0X82,0XF8,0X80,0X90};
     code char seg[10]={0xC0,0XF9,0XA4,0XB0,0X99,0X92,0X82,0XF8,0X80,0X90};
     char smgbuf[4]={1,2,3,4}; 
     extern unsigned int count;	
    
     void delay(unsigned int x)
     {
     	while(x)   
    	{
    		x--;
    	}	
     }
    
    void fill_smgbuf() 
    {
    	smgbuf[0]=count/1000;  
    	smgbuf[1]=(count%1000)/100;  
    	smgbuf[2]=((count%1000)%100)/10;   
    	smgbuf[3]=((count%1000)%100)%10;   
    }
    
    void load_smg()   
     {
     	char i;
    	fill_smgbuf();
     	for(i=0;i<4;i++)	
    	{
    		P0=0xFF;   
    		P0 = seg[smgbuf[i]];
    		P2 = ~(1<<i);
    		delay(200);
    	}
     }
    key.c文件

    #include "reg51.h"
    extern delay(unsigned int x);
    extern unsigned int count;
    sbit K1=P2^4;  //+1
    sbit K2=P2^5;	//-1
    
    void key1()
    {
    	static char st;
    	if(K1==0)
    	{
    		if(st==0)
    		{
    			delay(5000);
    			if(K1==0)
    			{
    				st=1;
    				count++;
    			}
    		}
    	}
    	else
    	{
    		st=0;
    	}
    }
    
    void key2()
    {
    	static char st;
    	if(K2==0)
    	{
    		 if(st==0)
    		 {
    		 	delay(5000);
    			if(K2==0)
    			{
    				st=1;
    				count--;
    			}
    		 }
    	}
    	else
    	{
    		st=0;
    	}
    }
    这个实验有一个地方需要注意,即每当复位,按K2键,数码管显示535。这是因为开机后程序默认count值为0,即16个0。当按下K2键后,减1,变成负1。而负1在内存中为16个1(负数的补码为其绝对值的原码取反再加1),又因为count定义为unsigned,所以显然count为65535。65535再被1000除,得到了535。

    总结:

    1、掌握数的分离方法,例如求出5678的个十百千位。

    2、C语言数组是数据批量处理的基础。

    3、尝试分别用四个键去控制个十百千四个数码管的闪烁。





    展开全文
  • 单片机中内存空间很小,主要是为了省硬件成本,如: coterx m3/m4 的 64KB/192KB, Quectel M26 的 256KB/512KB 等等都不带动态内存管理。为了实现内存动态分配和释放,要么使用厂家库的动态内存管理接口,要么自己...

    ================================================================================
    标题: 用 C 语言实现单片机中的动态内存管理
    作者: 叶飞虎
    日期: 2018.12.16
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        在单片机中内存空间很小,主要是为了省硬件成本,如: coterx m3/m4 的 64KB/192KB, Quectel M26 的 256KB/512KB 等等都不带动态内存管理。为了实现内存动态分配和释放,要么使用厂家库的动态内存管理接口,要么自己写动态内存管理。

        在不同单片机中有些只有片内存储单元,而有些可支持多个片外RAM,而且片外尺寸要大很多,有 1MB/2MB 等等。需要把片内和片外内存都管理起来,且要支持不定尺寸的内存分配和释放,所以自己就设计开发了内存管理,为了与 C 中的 malloc 和 free 一样好用就把分配和释放内存函数定义如下:

    // 分配指定尺寸的内存,若返回值非 NULL 则为分配成功的内存地址,否则分配失败
    void* Malloc(int ASize);
    
    // 释放已分配的内存
    void  Free(void* AMemory);
    

        本内存管理算法采用 bit 位的完全二叉树来管理分配和释放,而内存管理所需要的另外内存开销为每 1KB 需要 16 字节,且内存分配的最小单元为 16 字节,即使分配 1 字节也占用 16 字节空间。其实最小单元字节数可以根据自己需要来调整,最小单元字节数越大则内存管理的额外开销就越小,如果最小单元字节数为 32 时 1KB 的开销只要 8 字节。若分配尺寸在最小单元字节数之内(含)时,只需要 1bit 来做标志;否则就需要 2bits 来做标志,在二叉树的分支结点和叶结点各一个 bit 位来做标志,分支结点对应分配内存的起始点,而叶结点为分配内存的最后一个单元对应点。

        下面通过举例来说明内存分配的过程及局限性:

    例一
       若内存管理只有 1KB 空间,则下面按步骤来描述可分配及分配占用情况:
       1. [0..1023: 空闲] 可分配尺寸区间为 [1..1024];
       2. Malloc(700) 分配成功且空间占用情况如下:
          [0..703: 占用],[704..767: 空闲],[768..1023: 空闲]
          这时的可分配尺寸区间为 [1..256];
       3. Malloc(300) 分配失败,因为 300 超过可分配尺寸区间;
       4. Malloc(236) 分配成功且空间占用情况如下:
          [0..703: 占用],[704..767: 空闲],[768..1007: 占用],[1008..1023: 空闲]
          这时的可分配尺寸区间为 [1..64];
       5. Malloc(40) 分配成功且空间占用情况如下:
          [0..703: 占用],[704..751: 占用],[752..767: 空闲],[768..1007: 占用],[1008..1023: 空闲]
          这时的可分配尺寸区间为 [1..16];
       6. Malloc(16) 分配成功且空间占用情况如下:
          [0..703: 占用],[704..751: 占用],[752..767: 空闲],[768..1007: 占用],[1008..1023: 占用]
          这时的可分配尺寸区间为 [1..16];
       7. Malloc(16) 分配成功且空间占用情况如下:
          [0..703: 占用],[704..751: 占用],[752..767: 占用],[768..1007: 占用],[1008..1023: 占用]
          这时的可分配尺寸为 0,分配情况为:{700, 236, 40, 16, 16}。

    例二
       若内存管理只有 14KB 空间,则下面按步骤来描述可分配及分配占用情况:
       1. [2KB: 空闲],[4KB: 空闲],[8KB: 空闲], 可分配尺寸区间为 [1..8KB];
       2. Malloc(10<<10) 分配失败,因为 10KB 超过可分配尺寸区间;
       3. Malloc(7<<10) 分配成功且空间占用情况如下:
          [2KB: 空闲],[4KB: 空闲],[0..7KB-1: 占用],[7KB..8KB-1: 空闲]
          这时的可分配尺寸区间为 [1..4KB];
       4. Malloc(3<<10) 分配成功且空间占用情况如下:
          [2KB: 空闲],[0..3KB-1: 占用],[3KB..4KB-1: 空闲],[0..7KB-1: 占用],[7KB..8KB-1: 空闲]
          这时的可分配尺寸区间为 [1..2KB];
       5. Malloc(1<<10) 分配成功且空间占用情况如下:
          [0..1KB-1: 空闲],[1KB..2KB-1: 占用],[0..3KB-1: 占用],..,[7KB..8KB-1: 空闲]
          这时的可分配尺寸区间为 [1..1KB];
       6. Malloc(1<<10) 分配成功且空间占用情况如下:
          [0..1KB-1: 占用],[1KB..2KB-1: 占用],[0..3KB-1: 占用],..,[7KB..8KB-1: 空闲]
          这时的可分配尺寸区间为 [1..1KB];
       ... ...


    内存管理的头文件(KYMemory.h)如下:

    // =======================================
    // Unit   : memory manager (内存管理单元)
    // Version: 4.0.1.0 (build 2018.12.16)
    // Author : Kyee Ye
    // Email  : kyee_ye(at)126.com
    // Copyright (C) Kyee workroom
    // =======================================
    
    #ifndef _KYMemory_H_
    #define _KYMemory_H_
    
    #ifdef __cplusplus
    extern "C"
    {
    #endif
    
    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    /* 内存分配/释放函数 */
    
    // 分配指定尺寸的内存
    void* Malloc(int ASize);
    
    // 释放已分配的内存
    void  Free(void* AMemory);
    
    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    /* 内存管理的相关函数 */
    
    // ---------------- 内存管理使用示例 ----------------
    
    /*
    // 1KB 内存项数
    #define _1KB_Count1 64
    
    // 索引项的全局占用空间
    static T1KBIndex  L_Indexs[_1KB_Count1];
    
    // 内存项的全局占用空间
    static T1KBItem   L_Items[_1KB_Count1];
    
    // 内存块列表
    static TMemBlock  L_Blocks[] = {{_1KB_Count1, L_Items, L_Indexs}};
    
    // 内存管理的全局加锁
    static void L_Lock(void)
    {
       // ... ...
    }
    
    // 内存管理的全局解锁
    static void L_Unlock(void)
    {
       // ... ...
    }
    
    // 扩展内存分配
    static void* L_Malloc(int ASize)
    {
       return malloc(ASize);
    }
    
    // 扩展内存释放
    static void L_Free(void* AMemory)
    {
       free(AMemory);
    }
    
    // on-init: 在工程的初始化函数中
    {
       // 初始化内存管理
       KYMemory_Init(&L_Lock, &L_Unlock, &L_Malloc, &L_Free,
                      L_Blocks, sizeof(L_Blocks)/sizeof(L_Blocks[0]));
    
       // 其它初始化
       ... ...
    }
    
    // on-fini: 在工程的结束函数中
    {
       // 其它释放
       ... ...
    
       // 释放内存管理
       KYMemory_Fini();
    }
    */
    
    // ---------------- 类型定义 ----------------
    
    // 内存管理的内存项/索引项类型
    typedef unsigned char T1KBItem[1<<10]; // 1KB 的内存项
    typedef unsigned char T1KBIndex[1<<4]; // 1KB 的索引项
    
    // 锁的回调函数指针类型
    typedef void  (*TLockFunc)(void);
    
    // 第三方内存分配/释放函数指针类型
    typedef void* (*TMalloc)(int ASize);
    typedef void  (*TFree)(void* AMemory);
    
    // 内存块项结构
    typedef struct
    {
       int         Count;      // 内存块的 1KB 内存项数
       T1KBItem*   Items;      // 内存块的内存项列表, 项数为: Count
       T1KBIndex*  Indexs;     // 内存块的索引项列表, 项数为: Count
    } TMemBlock;
    
    // ---------------- 构造函数和析构函数 ----------------
    
    /* 内存管理初始化
    参数:
       ALock    加锁函数
       AUnlock  解锁函数
       AMalloc  第三方内存分配函数, 注: 不允许为当前的 Malloc 函数
       AFree    第三方内存释放函数, 注: 不允许为当前的 Free 函数
       ABlocks  内存块列表, 列表项数为: ACount
       ACount   内存块的项数
    返回值:
       >0       初始化成功, 返回内存块的项数
        0       初始化失败, 无合法内存块
       -1       初始化失败, 参数不合法
       -2       初始化失败, 表示已初始化
       -3       初始化失败, 表示正在初始化
    */
    int   KYMemory_Init(TLockFunc  ALock,   TLockFunc AUnlock,
                        TMalloc    AMalloc, TFree     AFree,
                        TMemBlock* ABlocks, int       ACount);
    
    // 内存管理释放
    void  KYMemory_Fini(void);
    
    // ---------------- 属性方法 ----------------
    
    // 1KB 内存的总项数, 若未初始化则返回值为 -1
    int   KYMemory_1KBCount(void);
    
    // 内存的总单元个数(注: 每个单元 16 字节), 若未初始化则返回值为 -1
    int   KYMemory_CellCount(void);
    
    // 内存的已分配单元个数(注: 每个单元 16 字节), 若未初始化则返回值为 -1
    int   KYMemory_UsedCount(void);
    
    // 内存块列表的项数, 若未初始化则返回值为 -1
    int   KYMemory_BlockCount(void);
    
    #ifdef __cplusplus
    }     // end extern "C"
    #endif
    
    #endif
    

       本内存管理单元已用于生产,可靠性都已经过严格的压力测试,现公开内存管理的源码,而源码文件(KYMemory.c)因涉及版权原因已做了代码混淆处理,但不影响使用。

    内存管理单元代码(KYMemory.rar)下载如下:
    https://pan.baidu.com/s/1eTWTYD0#list/path=%2Fshares%2Fsources%2F_open&parentPath=%2F

    展开全文
  • C语言内存管理的艺术

    2013-09-05 09:54:03
    C语言内存管理的艺术  本节知识点: 1.栈的知识(我觉得栈是本节很头疼的一个问题):  对于栈的问题,首先我们通过几个不同的角度来看(因为思维有些小乱所以我们通过分总的形式进行阐述):  a.sp堆栈指针,...

    先声明一下, 这文章不是我写的, 是我转载的别人的文章,写的很好, 这里只是给自己记个笔记 。

    C语言内存管理的艺术  

    本节知识点:

    1.栈的知识(我觉得栈是本节很头疼的一个问题)

        对于栈的问题,首先我们通过几个不同的角度来看(因为思维有些小乱所以我们通过分总的形式进行阐述)

        a.sp堆栈指针,相信学过51单片机,学过arm裸机的人都知道这个堆栈指针。我们现在从51单片机的角度来看这个堆栈指针寄存器。这个堆栈指针的目的是什么?是用来保护现场(子函数的调用)和保护断点(中断的处理)的,所以在处理中断前,调用子函数前,都应该把现场和返回地址压入栈中。而且堆栈还会用于一些临时数据的存放51中的sp指针再单片机复位的时候初值为0x07常常我们会把这个sp指针指向0x30,因为0x30~0x7f是用户RAM(专门为堆栈准备的存储区)。然后要引入一个栈顶和栈底的概念。栈操作的一段叫栈顶(这里是sp指针移动的那个位置,sp也叫栈顶指针)sp指针被赋初值的那个地址叫栈底(这里是0x30是栈底,因为栈顶永远会只在0x30栈底的一侧进行移动,不会在两层移动)。而且51单片机的sp是向上增长的,叫做向上增长型堆栈(栈顶指针sp向高地址处进行增长)。因为PUSH压栈操作,是sp指针先加1(指向的地址就增大一个),再压入一个字节,POP弹出操作,先弹出一个字节,sp再减1(指向的地址就减少一个)。看PUSHPOP的过程,可见是一个满堆栈(满堆栈的介绍在后面)。小结一下:51的堆栈是一个向上增长型的满堆栈

        b.对于arm来说,大量的分析过程都与上面相同。只是堆栈不再仅仅局限于处理中断了,而是处理异常。arm的堆栈有四种增长方式(具体见d)。注意:在arm写裸机的时候,那个ldr sp, =8*1024   其实是在初始化栈底。sp是栈顶指针,当没有使用堆栈的时候,栈顶指针是指向栈底的。当数据来的时候,每次都是从栈顶进入的(因为栈的操作入口在栈顶),然后sp栈顶指针指向栈顶,慢慢远离栈底。说这些是想好好理解下什么是栈顶,什么是栈底。

        c.对于8086来说,它的栈的生长方向也是从高地址到低地址,每次栈操作都是以字(两个字节)为单位的。压栈的时候,sp先减2,出栈的时候,sp再加2。可见8086的堆栈是一个向下增长型的满堆栈

        d.总结下:

                        (1).当堆栈指针sp指向,最后一个压入堆栈的数据的时候,叫满堆栈。

                        (2).当堆栈指针sp指向,下一个要放入数据的空位置的时候,叫空堆栈。如下图:

                      (3).当堆栈由低地址向高地址生长的时候,叫向上生长型堆栈即递增堆栈。

                      (4).当堆栈由高地址向低地址生长的时候,叫向下生长型堆栈即递减堆栈。如图:

                     (5). 所以说arm堆栈支持四种增长方式:满递减栈(常用的ARMThumb c/c++编译器都使用这个方式,也就是说如果你的程序中不是纯汇编写的,有c语言就得使用这种堆栈形式)、满递增栈、空递减栈、空递增栈。这四种方式分别有各自的压栈指令,出栈指针,如下图:  

               e.对于裸机驱动程序(51ARM)没有操作系统的,编译器(keilarm-linux-gcc),会给sp指针寄存器一个地址。然后一切的函数调用,中断处理,这些需要的现场保护啊,数据啊都压入这个sp指向的栈空间。(arm.s文件是自己写的,sp是自己指定的,编译器会根据这个sp寄存器的值进行压栈和出栈,但是压栈和出栈的规则是满递减栈的规则,因为arm-linux-gcc是这个方式的,所以在汇编调用c函数的时候,汇编代码必须使用满递减栈的那套压栈出栈指令)。这种没有操作系统的裸机驱动程序,只有一个栈空间,就是sp指针指向的那个栈空间。

               f.对于在操作系统上面的程序,里面涉及内存管理、虚拟内存、编译原理的问题。首先说不管是linux还是windows的进程的内存空间都是独立的,linux是前3Gwindows4G,这都是虚拟内存的功劳。那编译器给程序分配的栈空间,在程序运行时也是独立的。每一个进程中的栈空间,应该都是在使用sp指针(但是在进程切换的过程中,sp指针是怎么切换的我就不清楚了,这个应该去看看操作系统原理类的书)Ps:对于x8632位机来说不再是spbp指针了,而是espebp两个指针。有人说程序中的栈是怎么生长的,是由编译器决定的,有人说是由操作系统决定的!!!我觉得都不对,应该是由硬件决定的,因为cpu已经决定了sp指针的压栈出栈方式。只要你操作系统在进程运行的过程中,使用的这个栈是sp栈指针指向的(即使用了sp指针),而不是自己定义的一块内存(sp指针无关的话)  Ps:实际中进程使用的是espebp两个指针,这里仅仅用sp是想说明那个意思而已!  操作系统使用的栈空间就必须符合sp指针的压栈和出栈方式,也就是遵循了cpu决定的栈的生长方式。编译器要想编译出能在这个操作系统平台上使用的程序,也必须要遵守这个规则,所以来看这个栈的生长方式是由cpu决定的。这也是为什么我用那么长的篇幅来解释sp指针是怎么工作的原因!

              g.要记住,由于操作系统有虚拟内存这个东东,所以不要再纠结编译器分配的空间在操作系统中,进程执行的时候空间是怎么用的了。编译器分配的是什么地址,进程中使用这个变量的虚拟地址就是什么!是对应的。当然有的时候,编译器也会耍些小聪明。不同编译器对栈空间上的变量分配的地址可能不一样,但方向一定是一样的(因为这个方向是cpu决定,编译器是无权决定的,是sp指针压栈的方向),如图:

    1和图2的共同点是:都是从高地址处到低地址处,因为sp指针把ABC变量压入栈的方向就是从高到低地址的。这个是什么编译器都不会变的。

    1和图2的不同点是:图2进行了编译器的小聪明,它在给ABC开辟空间的时候,不是连续开辟的空间,有空闲(其实依然进行了压栈操作只是压入的是0或者是ff),这样变量直接有间隙就避免了,数组越界,内存越界造成的问题。切记在获取ABC变量的时候,不是通过sp指针,而是通过变量的地址获得的啊,sp只负责把他们压入栈中,即给他们分配内存

                   h.说了那么多栈的原理,现在我们说说栈在函数中究竟起到什么作用:保存活动记录!!!如图:

    注意:活动记录是什么上面的这个图已经说的很清楚了,如果再调用函数,这个活动记录会变成什么样呢?会在这个活动记录后面继续添加活动记录(这个活动记录是子函数的活动记录),增加栈空间,当子函数结束后,子函数的活动记录清除,栈空间继续回到上图状态!

    Ps:活动记录如下:

             i.函数的调用行为

    函数的调用行为中有一个很重要的东西,叫做调用约定。调用约定包含两个约定。

    第一个是:参数的传递顺序(这个不是固定的,是在编译器中约定好的),从左到右依次入栈:__stdcall__cdecl__thiscall   (这些指令,直接写在函数名的前面就可以,但是跟编译器有点关系,可能会有的编译器不支持会报错)

                                                                                                                           从右到左依次入栈:__pascal__fastcall

    第二个是:堆栈的清理(这段代码也是编译器自己添加上的):调用者清理

                                                                                                        被调用者函数返回后清理

    注意:一般我们都在同一个编译器下编译不会出这个问题。 但是如果是调用动态链接库,恰巧编译动态链接库的编译器跟你的编译器的默认约定不一样,那就惨了!!!或者说如果动态链接库的编写语言跟你的语言都不一样呢?                 

              j.这里要声明一个问题:就是栈的增长方向是固定的,是cpu决定的。但是不代表说你定义的局部变量也一定是先定义的在高地址,后定义的在低地址,局部变量之间都是连续的(这个在上面已经说过了是编译器决定的),还有就是栈的增长方向也决定不了参数的传递顺序(这个是调用约定,通过编译器的手处理的)。下面让我们探索下再dev c++中,局部变量的地址问题。

    [cpp] view plaincopy

    #include <stdio.h>  

      

    void fun()  

    {  

        int a;  

        int b;  

        int c;  

        printf("funa  %p\n",&a);  

        printf("funb  %p\n",&b);  

    10     printf("func  %p\n",&c);  

    11 }  

    12 void main()  

    13 {  

    14     int a;  

    15     int b;  

    16     int c;  

    17     int d;  

    18     int e;  

    19     int f;  

    20     int p[100];  

    21       

    22     printf("a  %p\n",&a);  

    23     printf("b  %p\n",&b);  

    24     printf("c  %p\n",&c);  

    25     printf("d  %p\n",&d);  

    26     printf("e  %p\n",&e);  

    27     printf("f  %p\n",&f);  

    28     printf("p0    %p\n",&p[0]);  

    29     printf("p1    %p\n",&p[1]);  

    30     printf("p2    %p\n",&p[2]);  

    31     printf("p3    %p\n",&p[3]);  

    32     printf("p4    %p\n",&p[4]);  

    33                       

    34     printf("p10    %p\n",&p[10]);  

    35     printf("p20    %p\n",&p[20]);  

    36     printf("p30    %p\n",&p[30]);  

    37     printf("p80    %p\n",&p[80]);  

    38     printf("p90    %p\n",&p[90]);  

    39     printf("p100    %p\n",&p[100]);  

    40                   

    41       

    42     fun();  

    43       

    44 }  

    运行结果如下(不同编译器的运行结果是不一样的)


    通过上面的运行结果,可以分析得出:在同一个函数中,先定义的变量在高地址处,后定义的变量在低地址处,且他们的地址是相连的中间没有空隙。定义的数组是下标大的在高地址处,下标小的在低地址处(由此可以推断出malloc开辟出的推空间,也应该是下标大的在高地址处,下标小的在低地址处)子函数中的变量,跟父函数中的变量的地址之间有很大的一块空间,这块空间应该是两个函数的其他活动记录,且父函数中变量在高地址处,子函数中的变量在低地址处

                 k.下面来一个栈空间数组越界的问题,让大家理解一下,越界的危害,代码如下(猜猜输出结构)

    [cpp] view plaincopy

    45 #include<stdio.h>  

    46 /*这是一个死循环*/  

    47 /*这里面有数组越界的问题*/  

    48 /*有栈空间分配的问题*/  

    49 int main()  

    50 {  

    51       

    52     int i;  

    53 //  int c;  

    54     int a[5];  

    55     int c;  

    56     printf("i %p,a[5] %p\n",&i,&a[5]); //观察栈空间是怎么分配的  这跟编译器有关系的  

    57     printf("c %p,a[0] %p\n",&c,&a[0]);  

    58     for(i=0;i<=5;i++)  

    59     {  

    60         a[i]=-i;  

    61         printf("%d,%d",a[i],i);  

    62     }  

    63     return 1;  

    64 }  

    注意:不同编译器可能结果不一样,比如说vs2008就不会死循环,那是因为vs2008耍了我上面说的那个小聪明(就是局部变量和数组直接有间隙不是相连的,就避开了越界问题,但是如果越界多了也不行),建议在vc6dev c++中编译看结果。
                l.最后说说数据结构中的栈,其实数据结构中的栈就是一个线性表,且这个线性表只有一个入口和出口叫做栈顶,还是LIFO(后进先出的)结构而已。

                对栈的总结:之前就说过了那么多种栈的细节,现在在宏观的角度来看,其实栈就是一种线性的后进先出的结构,只是不同场合用处不同而已!

    2.堆空间:堆空间弥补了栈空间在函数返回后,内存就不能使用的缺陷。是需要程序员自行跟操作系统申请的。

    3.静态存储区:程序在编译期,静态存储区的大小就确定了   

    4.对于程序中的内存分布:请看这篇文章<c语言中的内存布局>

    5.对于内存对齐的问题:请看这篇文章<C语言深度解剖读书笔记(3.结构体中内存对齐问题)>

    6.使用内存的好习惯:

        a.定义指针变量的时候,最好是初始化为NULL,用完指针后,最好也赋值为NULL

        b.在函数中使用指针尽可能的,去检测指针的有效性

        c.malloc分配的时候,注意判断是否分配内存成功。

        d.malloc后记得free,防止内存泄漏!

        e.free(p)后应该p=NULL

        f.不要进行多次free

        g.不要使用free后的指针

        h.牢记数组的长度,防止数组越界

    7.内存常见的六个问题:

        a.野指针问题 :一个指针没有指向一个合法的地址

        b.为指针分配的内存太小

        c.内存分配成功,但忘记初始化,memset的妙用

        e.内存越界

        f.内存泄漏

        g.内存已经被释放 还仍然在使用(栈返回值问题)

    展开全文
  • 28.C语言内存管理机制

    2016-09-23 07:44:11
    28.1.内存的重要性及内存管理 28.2.栈的详解 28.2.栈的详解 28.4.堆内存使用范例 28.5.malloc的一些细节表现 28.6.代码段和.data段及bss段 28.7.C语言中变量和常量内存分配
  • 如何入门单片机C语言

    2020-07-14 13:47:24
    一、为什么要学单片机技术? 传统的电子产品升级改造成智能化的电子产品需要用到单片机技术。也就是说传统的电子产品如电视机、电子表、计算器、数码相机、手机、MP3、遥控器、洗衣机等产品智能化、微型化,需要的...
  • C语言内存初步框架了解

    千次阅读 多人点赞 2019-03-28 12:55:50
    ▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼分享一个大神朋友的人工智能教程。零基础!通俗易懂!风趣幽默!还带黄段子!...
  • C语言实现简单内存

    2017-08-20 23:53:13
    内存池的使用时很有必要的,因为他可以尽很大可能避免我们的内存碎片化,对于我们开辟小块内存的时候,他的作用尤为明显。我也实现了一个内存池,本来想和杨老师的一样高大上,但是他的代码,我实在是看不懂了,...
  • 51单片机内存管理

    千次阅读 2016-12-01 10:35:52
    1、在我们经常使用的51单片机中,使用的Memory Address 的选项中有pData,xData,Data这三种选择 我们知道xData 是外部的RAM的储存位置 2、但是关于stm32fxx系列的单片机就没有这么变态的做法了 3、具体介绍51单片机...
  • 内存管理机制,c语言和Python的内存管理机制是不一样的! c语言: 我们在写c语言时,可以通过指令直接访问内存,比如对单片机进行c语言编程时,可以将运行时的数据直接保存到内存中,如果底层单片机程序需要更新时...
  • C语言精讲之内存

    2020-07-11 17:07:19
    一、内存这个大话题 1.1、程序运行为什么需要内存 1.1.1、计算机程序运行的目的 计算机是用来干什么的?我们为什么需要不断编写程序?计算机就是用来计算的,而计算的话,我们就需要给它数据,最终通过计算得到一个...
  • 与之前的单片机内存管理实现思路不同 前者为将内存区域划分为固定大小的块,通过每个块的状态来分配内存,获取到的是连续的内存块,则相应的,内存也是连续的。 此次新的内存管理是将整个待分配的内存块看作一个...
  • C语言内存分配(栈、堆、段) 1、栈:保存局部变量、函数形参、自动变量(一般未加 static 的局部变量都是自动变量) 特点:先进后出、空间由系统管理 栈区生命周期在函数调用结束后释放 栈区保存的局部变量未初始...
  • 单片机软件设计架构(C语言)

    万次阅读 2008-11-25 14:31:00
    C语言嵌入式系统编程修炼之一:背景篇不同于一般形式的软件编程,嵌入式系统编程建立在特定的硬件平台上,势必要求其编程语言具备较强的硬件直接操作能力。无疑,汇编语言具备这样的特质。但是,归因于汇编语言开发...
  • 31、电源管理寄存器PCON:PCON用来管理单片机的电源部分,包括上电复位检测、掉电模式、空闲模式等。单片机复位时PCON全部被清0。D7 D6 D5 D4 D3 D2 D1 D0SMOD (SMOD1)(LVDF)(POF)GF1 GF0
  • 单片机c语言+编程c语言 除非它的工作要比大多数其他任何事物(尤其是计算机技术)都要好,否则它不会存在50年。 自1972年以来,C编程语言就一直活跃起来,并且一直作为我们软件定义世界的基本组成部分之一。 但是...
  • 1、前言 因为在校的原因,C语言学了快两年,但偶尔没敲代码,一下就...内存真的是一个聊不完的东西,不管是构成其的器件RAM,和CPU的关系,还是其管理方式等,是很多人的痛点。先时刻铭记一句话:计算机中的程序和数...
  • 单片机c语言+编程c语言C is probably the most widely known programming language. It is used as the reference language for computer science courses all over the world, and it’s probably the language ...
1 2 3 4 5 ... 20
收藏数 4,624
精华内容 1,849
关键字:

单片机c语言内存管理