2018-12-20 09:51:42 kyee 阅读数 465

================================================================================
标题: 用 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

2015-08-25 21:45:50 u011833609 阅读数 1853

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


与之前的单片机内存管理实现思路不同


前者为将内存区域划分为固定大小的块,通过每个块的状态来分配内存,获取到的是连续的内存块,则相应的,内存也是连续的。


此次新的内存管理是将整个待分配的内存块看作一个空闲内存描述结构加内存区域,申请一块内存是将空闲且大小合适的内存一分为二或整块获取来实现,释放内存是将当前内存与前后空闲内存合并完成的,有双向链表的意思。


memory.h文件

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

#ifndef __MEMORY_H__
#define __MEMORY_H__

#include "stm32f10x.h"
//创建一个内存区域
void DynMemCreate(void *pMem, uint32_t size);
//返回申请到的内存地址,若返回空,则申请失败
void *DynMemGet(uint32_t size);
//释放申请到的内存区域
void DynMemPut(void *pMem);

#endif //__MEMORY_H__

memory.c文件

#include "stdio.h"
#include "string.h"
#include "memory.h"

typedef enum
{
    DYNMEM_FREE,
    DYNMEM_USED,
}DYNMEM_USE_STATE;

typedef struct
{
    DYNMEM_USE_STATE used;
    uint32_t size;
    void *   pPrev;
    void *   pNext;
}DynMem_TypeDef;

static DynMem_TypeDef * pDynMem = NULL;

void DynMemCreate(void *pMem, uint32_t size)
{
    if(NULL == pMem)
        return;
    if(size <= sizeof(DynMem_TypeDef))
        return ;
    
    pDynMem = pMem;
    pDynMem->used = DYNMEM_FREE;
    pDynMem->size = size - sizeof(DynMem_TypeDef);
    pDynMem->pPrev = NULL;
    pDynMem->pNext = NULL;
}

//若返回空,则申请失败
void *DynMemGet(uint32_t size)
{
    DynMem_TypeDef * pGet = pDynMem;
    DynMem_TypeDef * pFree = NULL;
    
    if(size == 0)
        return NULL;
    
    size = (size + 3) / 4 * 4;
    
    while(pGet != NULL)
    {//找到合适空间
        if((pGet->used != DYNMEM_FREE) || (pGet->size < size))
        {
            pGet = pGet->pNext;
            continue;
        }
        
        //找到空闲且大小合适的内存区域
        if(pGet->size - size > sizeof(DynMem_TypeDef))
        {//可以放下一个新的内存块表,将其一分为二
            pFree = (DynMem_TypeDef*)((uint32_t)pGet + sizeof(DynMem_TypeDef) + size);
            
            /*修改链表关系*/
            /*Step 1*/
            pFree->pPrev = pGet;
            /*Step 2*/
            pFree->pNext = pGet->pNext;
            /*Step 3*/
            pGet->pNext = pFree;
            /*Step 4*/
            if(NULL != pFree->pNext)
            {
                (*(DynMem_TypeDef*)(pFree->pNext)).pPrev = pFree;
            }
            
            /*剩余空间配置*/
            pFree->used = DYNMEM_FREE;
            pFree->size = pGet->size - size - sizeof(DynMem_TypeDef);
            /*获取到的空间配置*/
            pGet->used = DYNMEM_USED;
            pGet->size = size;
        }
        else
        {//已经放不下一个新的内存块表,将其全部占有
            pGet->used = DYNMEM_USED;
        }
        
        return (void*)((uint32_t)pGet + sizeof(DynMem_TypeDef));
    }
    
    return NULL;
}

void DynMemPut(void *pMem)
{
    DynMem_TypeDef * pPut = NULL;
    
    if(NULL == pMem)
        return;
    
    pPut = (DynMem_TypeDef*)((uint32_t)pMem - sizeof(DynMem_TypeDef));
    pPut->used = DYNMEM_FREE;
    
    //释放空间,整体释放/合二为一/合三为一
    if((pPut->pPrev != NULL) && (((DynMem_TypeDef*)(pPut->pPrev))->used == DYNMEM_FREE))
    {
        ((DynMem_TypeDef*)(pPut->pPrev))->size += sizeof(DynMem_TypeDef) + pPut->size;
        ((DynMem_TypeDef*)(pPut->pPrev))->pNext = pPut->pNext;
        
        if(pPut->pNext != NULL)
        {
            ((DynMem_TypeDef*)(pPut->pNext))->pPrev = pPut->pPrev;
        }
        
        pPut = (DynMem_TypeDef*)(pPut->pPrev);
    }
    if((pPut->pNext != NULL) && (((DynMem_TypeDef*)(pPut->pNext))->used == DYNMEM_FREE))
    {
        pPut->size += sizeof(DynMem_TypeDef) + ((DynMem_TypeDef*)(pPut->pNext))->size;
        pPut->pNext = ((DynMem_TypeDef*)(pPut->pNext))->pNext;
        
        if(pPut->pNext != NULL)
        {
            ((DynMem_TypeDef*)(pPut->pNext))->pPrev = pPut;
        }
    }
}


初步测试通过,未经过详细测试,如有Bug,尊听点评。






2015-07-10 19:13:41 u011833609 阅读数 4503

单片机简单内存管理器

本代码基于无操作系统的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;
}


2013-09-05 09:54:03 tanghui20 阅读数 676

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

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.内存已经被释放 还仍然在使用(栈返回值问题)

2019-04-07 17:15:29 haohulala 阅读数 144

这是我突发奇想的一个项目,用c语言模拟51单片机的运行

整个项目分为三个部分

内存与寄存器模拟器

51汇编编译器

运行器

现在只完成了内存与寄存器部分,而且还只是模拟了部分寄存器,不过我感觉即使只是做成这样工作量也是超级大的。

不知道还会不会更新这个项目了,毕竟这一年时间很关健也很忙,不应该把时间浪费在没有意义的事情上。

也许等以后有空了会填上这个坑吧,也许永远也不会继续去写这个项目了。

下面上代码吧。

mm.h

//存储管理器的头文件
/*
    tiny51模拟的存储器 (内存的大小空间都是我们假设的,实际上要比这个从小得多)
    16KB的ROM 
    16KB的RAM 
    16KB的外接RAM
    目前打算模拟的寄存器有
    模拟的寄存器有
    ACC 累加器 
    PSW 程序状态字
    DPH 数据地址指针高8位
    DPL 数据地址指针低8位
    SP  堆栈指针 
    PC  程序计数器
*/

//最后修改时间 2019-4-7 12:15

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

//定义一些数据结构//

#define bool int

#define SIZE_OF_ROM 16384   //ROM的内存的大小
#define SIZE_OF_RAM 16384   //RAM的内存大小
#define SIZE_OF_XRAM 16384  //外部RAM的内存大小

enum typeOfMemory{  //内存中数据类型的枚举,后期还会再添加
    NONE,   //表示空内存
    CODE,   //代码
    DATA   //数据
};


/*
    ROM是代码存放的地址,也就是C:0x xxxxh 可以索引到的地址
    因为PC是16位的,所有ROM最大就是64KB
    当取常量表中数据的时候也可以DPTR作为指针,DPTR也是16位的
    16 * 1024 = 16384   //开16384个结构体数组模拟64KB的ROM
*/

struct ROM{
    enum typeOfMemory type;   //记录数据的类型
    int8_t opCode;  //当前内存存放的操作码
    int8_t length;  //数据的长度
    int16_t index;  //当前内存的地址索引,也就是数组索引
};   //可以索引的地址为0-65535 

/*
    RAM是代码可以操作的存储空间,也就是d:0x xxxxh 可以索引到的地址
    也就是用用MOV指令,当第一个操作数是指针或者直接地址的时候索引到的内存
    寄存器是8位的,所以可以索引的内存为128B
    假设直接地址可以索引16KB内存
    RAM区不需要长度,因为就是一个字节一个字节存的
*/
struct RAM{
    enum typeOfMemory type;  //这里只能是空或者数据
    char data;    //数据
    int16_t index;  //地址索引,虚拟机启动的时候要初始化
};

/*
    XRAM和RAM没有本质区别,用x:0x xxxxh 可以索引到地址
*/

struct XRAM{
    enum typeOfMemory type;
    char data;
    int16_t index;
};

/*
    寄存器还没写呢
*/
//程序状态字
struct PSW{
    bool CY;    //cy位,用于表示进位,借位
    bool AC;    //辅助进位标志位,当acc低4位发生进位时置该标志位
    bool F0;    //通用标志位,用户来定义
    bool RS1, RS0;  //用于指示使用哪一组工作寄存器
    bool OV;    //溢出标志位
    bool none;  //没有用的一位
    bool P;     //奇偶校验标志位
};

struct commonRegister{
    int8_t R[8];
};


struct SRF{     //特殊功能寄存器
    int8_t ACC; //8位的累加器
    int8_t DPH; //数据指针高8位
    int8_t DPL; //数据指针低8位
    int8_t SP;  //堆栈指针
    int16_t PC; //程序计数器,指向下一条将要执行的指令
};


/*
    这是内存管理器,实现对上述数据结构的管理
    所有的内存数据结构都放在这里
*/
struct memoryManager{   //这里面的内容都要初始化
    //内存模块
    struct ROM CodeMemory[SIZE_OF_ROM];
    struct RAM DataMemory[SIZE_OF_RAM];
    struct XRAM XDataMemory[SIZE_OF_XRAM];

    //特殊功能寄存器
    struct SRF srf;
    //程序状态字
    struct PSW psw;

    //通用寄存器
    struct commonRegister cr[4];   //一共四组

    bool tableOfROM[SIZE_OF_ROM];   //表示ROM空间实用情况的位图
    bool tableOfRAM[SIZE_OF_RAM];
    bool tableOfXRAM[SIZE_OF_XRAM];

    unsigned int leftROM;
    unsigned int leftRAM;
    unsigned int leftXRAM;
    
}mm;


//下面要写一些方法对内存单元和寄存器进行操作
//初始化内存单元
void initMemory();
//将一个字节写入内存单元
void writeROM_CODE(memoryManager &mm, int8_t info,int length, int16_t addr);
void writeROM_DATA(memoryManager &mm, int8_t info,int16_t addr);
void writeRAM(memoryManager &mm, char info, int16_t addr);
void writeXRAM(memoryManager &mm, char info, int16_t addr);
//对寄存器进行写操作
void writeACC(memoryManager &mm, int8_t info);
void writeDPH(memoryManager &mm, int8_t info);
void writeDPL(memoryManager &mm, int8_t info);
void writeSP(memoryManager &mm, int8_t info);
void writePC(memoryManager &mm, int8_t info);
//对程序状态字进行写操作
void writePSW_CY(memoryManager &mm, int8_t info);
void writePSW_AC(memoryManager &mm, int8_t info);
void writePSW_F0(memoryManager &mm, int8_t info);
void writePSW_RS1(memoryManager &mm, int8_t info);
void writePSW_RS0(memoryManager &mm, int8_t info);
void writePSW_OV(memoryManager &mm, int8_t info);
void writePSW_P(memoryManager &mm, int8_t info);

//写入通用寄存器
void writeCR(memoryManager &mm, int8_t info, int which);

//下面是读操作
int readROM(memoryManager mm, int16_t addr);
int readRAM(memoryManager mm, int16_t addr);
int readXRAM(memoryManager mm, int16_t addr);

int readACC(memoryManager mm);
int readDPH(memoryManager mm);
int readDPL(memoryManager mm);
int readSP(memoryManager mm);
int readPC(memoryManager mm);

int readPSW_CY(memoryManager mm);
int readPSW_AC(memoryManager mm);
int readPSW_F0(memoryManager mm);
int readPSW_RS1(memoryManager mm);
int readPSW_RS0(memoryManager mm);
int readPSW_OV(memoryManager mm);
int readPSW_P(memoryManager mm);

int readCR(memoryManager mm, int which);

mm.c

#ifndef _MM_H
#define _MM_H
#include "mm.h"
#include <stdio.h>
#include <stdlib.h>
/**
 * 目前先填这么多吧,感觉写这种代码真实浪费时间啊,这个文件是内存管理器提供给外部的方法
 * 看情况吧,应该是不是再写了
 * */
//初始化函数测试完毕,没有问题
void initMemory(){
    for(int i=0; i<SIZE_OF_ROM; i++){
        mm.CodeMemory[i].index = i;
        mm.CodeMemory[i].type = NONE;
        mm.CodeMemory[i].length = 1;
        mm.CodeMemory[i].opCode = 0;
        mm.tableOfROM[i] = 0;
    }
    for(int i=0; i<SIZE_OF_RAM; i++){
        mm.DataMemory[i].index = i;
        mm.DataMemory[i].type = NONE;
        mm.DataMemory[i].data = '0';
        mm.tableOfRAM[i] = 0;
    }
    for(int i=0; i<SIZE_OF_XRAM; i++){
        mm.XDataMemory[i].index = i;
        mm.XDataMemory[i].type = NONE;
        mm.XDataMemory[i].data = '0';
        mm.tableOfXRAM[i] = 0;
    }
    //初始化SRF
    mm.srf.ACC = 0;
    mm.srf.DPH = 0;
    mm.srf.DPL = 0;
    mm.srf.SP = 0;
    mm.srf.PC = 0;
    mm.psw.CY = 0;
    mm.psw.AC = 0;
    mm.psw.F0 = 0;
    mm.psw.RS1 = 0;
    mm.psw.RS0 = 0;
    mm.psw.OV = 0;
    mm.psw.P = 0;
    mm.psw.none = 0;

    //初始化通用寄存器
    for(int i=0; i<4; i++){
        for(int j=0; j<8; j++){
            mm.cr[i].R[j] = 0;
        }
    }

    mm.leftROM = SIZE_OF_ROM;
    mm.leftRAM = SIZE_OF_RAM;
    mm.leftXRAM = SIZE_OF_XRAM;
}

//下面写一些写内存的方法
void writeRAM(memoryManager &mm, char info, int16_t addr){
    //先判断有没有越界
    if(addr > SIZE_OF_RAM){
        printf("error: the address %#x is cross the border\n", addr);
        return;
    }
    if(mm.tableOfRAM[addr] == 0){   //如果这个内存没有被使用
        mm.leftRAM--;
        mm.tableOfRAM[addr] = 1;
    } else {    //如果被使用了就输出一个警告
        printf("warring: the address %#x has been used\n", addr);
    }
    //填入数据
    mm.DataMemory[addr].data = info;
    mm.DataMemory[addr].type = DATA;
    return;
}
//这个照搬上面的就行
void writeXRAM(memoryManager &mm, char info, int16_t addr){
    //先判断有没有越界
    if(addr > SIZE_OF_XRAM){
        printf("error: the address %#x is cross the border\n", addr);
        return;
    }
    if(mm.tableOfXRAM[addr] == 0){   //如果这个内存没有被使用
        mm.leftXRAM--;
        mm.tableOfXRAM[addr] = 1;
    } else {    //如果被使用了就输出一个警告
        printf("warring: the address %#x has been used\n", addr);
    }
    //填入数据
    mm.XDataMemory[addr].data = info;
    mm.XDataMemory[addr].type = DATA;
    return;
}
//下面是对RAM的操作,先是写代码,代码只能一次性读入,所有不检查之前有没有用过这个内存单元
void writeROM_CODE(memoryManager &mm, int8_t info,int length, int16_t addr){
    if(mm.leftRAM == 0){
        printf("error: the left room of ram is not enough\n");
    } 
    mm.CodeMemory[addr].type = CODE;
    mm.CodeMemory[addr].length = length;
    mm.CodeMemory[addr].opCode = info;  //写入代码
    return;
}
//写入数据(注意,使用这个方法前要把info转化成字符)
void writeROM_DATA(memoryManager &mm, int8_t info,int16_t addr){
    if(mm.leftRAM == 0){
        printf("error: the left room of ram is not enough\n");
    } 
    mm.CodeMemory[addr].type = DATA;
    mm.CodeMemory[addr].opCode = info;  //写入数据
    return;
}

//下面是写入寄存器的操作
void writeACC(memoryManager &mm, int8_t info){
    mm.srf.ACC = info;
    return;
}
void writeDPH(memoryManager &mm, int8_t info){
    mm.srf.DPH = info;
    return;
}
void writeDPL(memoryManager &mm, int8_t info){
    mm.srf.DPL = info;
    return;
}
void writeSP(memoryManager &mm, int8_t info){
    mm.srf.SP = info;
    return;
}
void writePC(memoryManager &mm, int8_t info){
    mm.srf.PC = info;
    return;
}

//下面是程序状态字,info只能是1或者0
void writePSW_CY(memoryManager &mm, int8_t info){
    if(info == 1 || info == 0){
        mm.psw.CY = info;
    } else {
        printf("error: the info should be bool type in order to write into the psw\n");
    }
}
void writePSW_AC(memoryManager &mm, int8_t info){
    if(info == 1 || info == 0){
        mm.psw.AC = info;
    } else {
        printf("error: the info should be bool type in order to write into the psw\n");
    }
}
void writePSW_F0(memoryManager &mm, int8_t info){
    if(info == 1 || info == 0){
        mm.psw.F0 = info;
    } else {
        printf("error: the info should be bool type in order to write into the psw\n");
    }
}
void writePSW_RS1(memoryManager &mm, int8_t info){
    if(info == 1 || info == 0){
        mm.psw.RS1 = info;
    } else {
        printf("error: the info should be bool type in order to write into the psw\n");
    }
}
void writePSW_RS0(memoryManager &mm, int8_t info){
    if(info == 1 || info == 0){
        mm.psw.RS0 = info;
    } else {
        printf("error: the info should be bool type in order to write into the psw\n");
    }
}
void writePSW_OV(memoryManager &mm, int8_t info){
    if(info == 1 || info == 0){
        mm.psw.OV = info;
    } else {
        printf("error: the info should be bool type in order to write into the psw\n");
    }
}
void writePSW_P(memoryManager &mm, int8_t info){
    if(info == 1 || info == 0){
        mm.psw.P = info;
    } else {
        printf("error: the info should be bool type in order to write into the psw\n");
    }
}

//写入通用寄存器
void writeCR(memoryManager &mm, int8_t info, int which){
    if(which > 7 && which < 0){
        printf("error: common register should be in [0, 8)\n");
        return;
    }
    //计算是哪一组通用寄存器
    int sum = mm.psw.RS0*1 + mm.psw.RS1*2;
    mm.cr[sum].R[which] = info;
}

//读操作(这里有一个问题,就是长度不为1的指令怎么读)
//测试通过
int readROM(memoryManager mm, int16_t addr){
    return mm.CodeMemory[addr].opCode;
}

int readRAM(memoryManager mm, int16_t addr){
    return mm.DataMemory[addr].data;
}

int readXRAM(memoryManager mm, int16_t addr){
    return mm.XDataMemory[addr].data;
}

int readACC(memoryManager mm){
    return mm.srf.ACC;
}
int readDPH(memoryManager mm){
    return mm.srf.DPH;
}
int readDPL(memoryManager mm){
    return mm.srf.DPL;
}
int readSP(memoryManager mm){
    return mm.srf.SP;
}
int readPC(memoryManager mm){
    return mm.srf.PC;
}

int readPSW_CY(memoryManager mm){
    return mm.psw.CY;
}
int readPSW_AC(memoryManager mm){
    return mm.psw.AC;   
}
int readPSW_F0(memoryManager mm){
    return mm.psw.F0;
}
int readPSW_RS1(memoryManager mm){
    return mm.psw.RS1;
}
int readPSW_RS0(memoryManager mm){
    return mm.psw.RS0;
}
int readPSW_OV(memoryManager mm){
    return mm.psw.OV;
}
int readPSW_P(memoryManager mm){
    return mm.psw.P;
}
int readCR(memoryManager mm, int which){
    if(which > 7 && which < 0){
        printf("error: common register should be in [0, 8)\n");
        return -65534;
    }
    int sum = readPSW_RS0(mm)*1 + readPSW_RS1(mm)*2;
    return mm.cr[sum].R[which];
}


#if DEBUG
//这段代码需要在编译的时候加上 -DDEBUG 否则会因为没有main而报错
int main(){
    initMemory();
    #if INIT
    for(int i=0; i<SIZE_OF_ROM; i++){
        printf("CodeMemory[%d].index = %d\n", i, mm.CodeMemory[i].index);
        printf("DataMemory[%d].index = %d\n", i, mm.DataMemory[i].index);
        printf("XDataMemory[%d].index = %d\n", i, mm.XDataMemory[i].index);
    }
    printf("%d\n", mm.srf.ACC);
    printf("%d\n", mm.srf.DPH);
    for(int i=0; i<4; i++){
        for(int j=0; j<8; j++){
           printf("%d ", mm.cr[i].R[j]);
        }
        printf("\n");
    }
    #endif
    #if WRITER
    writeRAM(mm, 48, 500);
    writeXRAM(mm, 50, 500);
    writeROM_CODE(mm, 32, 1, 500);
    writeROM_DATA(mm, int('a'), 501);
    printf("the info of RAM[500] is %c\n",readRAM(mm, 500));
    printf("the info of XRAM[500] is %c\n", readXRAM(mm, 500));
    printf("the info of ROM[500] is %d\n", readROM(mm, 500));
    printf("the info of ROM[501] is %d\n", readROM(mm, 501));
    #endif
    return 0;
}
#endif
#endif

编译的时候我是用g++编译的,因为gcc不识别引用符。

g++ mm.c -DDEBUG -DINIT -DWRITER -o mm.exe

C语言内存地址基础

阅读数 1854

我的C语言矩阵库02

阅读数 515

BIGO 面经C++总结

阅读数 162

没有更多推荐了,返回首页