给自己的学习总结帖-c内存2~~ 

一、C/C++内存管理

 C语言内存管理指对系统内存的分配、创建、使用这一系列操作。

    在计算机中,每个应用程序之间的内存是相互独立的,通常情况下应用程序 A 并不能访问应用程序 B,当然一些特殊技巧可以访问,但此文并不详细进行说明。例如在计算机中,一个视频播放程序与一个浏览器程序,它们的内存并不能访问,每个程序所拥有的内存是分区进行管理的。

    在计算机系统中,运行程序 A 将会在内存中开辟程序 A 的内存区域 1,运行程序 B 将会在内存中开辟程序 B 的内存区域 2,内存区域 1 与内存区域 2 之间逻辑分隔。

c语言基础4~内存2_内存碎片


 在程序 A 开辟的内存区域 1 会被分为几个区域,这就是内存四区,内存四区分为栈区、堆区、数据区与代码区。

c语言基础4~内存2_#define_02


   栈区指的是存储一些临时变量的区域,临时变量包括了局部变量、返回值、参数、返回地址等,当这些变量超出了当前作用域时将会自动弹出。该栈的最大存储是有大小的,该值固定,超过该大小将会造成栈溢出。

    堆区指的是一个比较大的内存空间,主要用于对动态内存的分配;在程序开发中一般是开发人员进行分配与释放,若在程序结束时都未释放,系统将会自动进行回收。

    数据区指的是主要存放全局变量、常量和静态变量的区域,数据区又可以进行划分,分为全局区与静态区。全局变量与静态变量将会存放至该区域。

    代码区就比较好理解了,主要是存储可执行代码,该区域的属性是只读的。

使用代码证实内存四区的底层结构

    由于栈区与堆区的底层结构比较直观的表现,在此使用代码只演示这两个概念。首先查看代码观察栈区的内存地址分配情况:

c语言基础4~内存2_#define_03

  运行结果为:  

c语言基础4~内存2_#define_04

     我们可以观察到变量 a 的地址是 2293324 变量 b 的地址是 2293320,由于 int 的数据大小为 4 所以两者之间间隔为 4;再查看变量 c,我们发现变量 c 的地址为 2293319,与变量 b 的地址 2293324 间隔 1,因为 c 的数据类型为 char,类型大小为 1。在此我们观察发现,明明我创建变量的时候顺序是 a 到 b 再到 c,为什么它们之间的地址不是增加而是减少呢?那是因为栈区的一种数据存储结构为先进后出,如图:

c语言基础4~内存2_#define_05

    首先栈的顶部为地址的“最小”索引,随后往下依次增大,但是由于堆栈的特殊存储结构,我们将变量 a 先进行存储,那么它的一个索引地址将会是最大的,随后依次减少;第二次存储的值是 b,该值的地址索引比 a 小,由于 int 的数据大小为 4,所以在 a 地址为 2293324 的基础上往上减少 4 为 2293320,在存储 c 的时候为 char,大小为 1,则地址为 2293319。由于 a、b、c 三个变量同属于一个栈内,所以它们地址的索引是连续性的。 

二、C语言malloc申请内存时的碎片问题

 解决问题:malloc在申请内存的时候,内存碎片问题会导致原本内存大小足够,却申请大内存失败;

    比如:原本内存还有10M内存,此时先申请4M内存,再申请16Bytes内存,之后把4M内存释放掉,按理来说,此时应该还有 10M - 16Bytes 内存,但此时,再去申请8M的大内存,则申请失败。

    因为malloc申请的内存,必须是一块连续的内存,但此时中间已经有16Bytes内存碎片导致内存不连续,所以申请内存失败;

c语言基础4~内存2_#define_06

以下是我针对碎片问题,对内存管理机制做出一种优化方案:在开机初始化内存之后,先申请一块1M左右内存(根据情况修改大小),用作内存碎片管理,然后把这1M内存分为很多个小内存,并把小内存的地址放在链接节点中,之后申请内存时,优先判断内存碎片管理中是否有满足大小的小内存。

    有的话,直接使用提前申请的小内存就可以了,如果内存管理机制中没有适合的内存,但重新用malloc()函数申请;

    接下来,解释我写的碎片管理机制:

1 mm_management_init()初始化函数

void mm_management_init(unsigned int free_memory_start, unsigned int free_memory_end)
  • 1.

    传入参数free_memory_start是内存初始化之后,剩余可申请的首地址,该地址,一般会传入到main函数,如果main()函数没有传入该参数的话,可以在内存初始化之后,自己malloc(4)申请一下,把返回的地址作为mm_management_init()函数的第一个参数;

    传入参数free_memory_end是可以申请的最大地址,每个IC各有不同;

    mm_management_init()对16bytes,64bytes,256bytes,512bytes,1024bytes,4096bytes这些小内存做优化,提前计算小内存占用的总大小。

    然后直接申请这块大内存占住,再把这块大内存分配给各个小内存,并记录在链表中,比如:mm_fix_16_head

2 mm_management_malloc()申请函数

unsigned int  mm_management_malloc(unsigned int size)
  • 1.

    申请内存的时候,先判断size大小,如果大小可以在内存管理机制中找到,则直接返回提前申请地址,如果大小不满足,或者小内存已被申请完,则用malloc重新申请。

    在内存管理机制中拿到的小内存,该链表节点的标记会设为MM_STATUS_BUSY。

3 mm_management_free()

void mm_management_free(void *mm_ptr)
  • 1.

    与mm_management_malloc()相反,先检查所有小内存链表是都有该地址,有的话就把该地址内存清0,并把标记设为MM_STATUS_FREE;如果是用malloc申请的,当时是free()释放掉;

    接下来是代码:

#include<stdio.h>
#include<malloc.h>

#define C_MM_16BYTE_NUM    (32)
#define C_MM_64BYTE_NUM    (16)
#define C_MM_256BYTE_NUM   (12)
#define C_MM_512BYTE_NUM   (12)
#define C_MM_1024BYTE_NUM   (18)
#define C_MM_4096BYTE_NUM   (30)

#define C_MM_16BYTE     (16)
#define C_MM_64BYTE     (64)
#define C_MM_256BYTE    (256)
#define C_MM_512BYTE    (512)
#define C_MM_1024BYTE    (1024)
#define C_MM_4096BYTE    (4096)

#define C_MM_MAX_SIZE    C_MM_4096BYTE //碎片管理最大的碎片大小

#define MM_STATUS_FREE    (0)   //0:表示内存空闲
#define MM_STATUS_BUSY    (1)   //1:表示内存已被申请

#define MM_STATUS_OK                (0)
#define MM_STATUS_FAIL              (1)

typedef struct mm_node_struct {
 unsigned int    *mm_node;   //存放内存节点指针
 unsigned short   iflag;    //指针是否空闲
 struct P_MM_Node_STRUCT *next;    //指向下一个内存节点指针
} MM_Node_STRUCT, *P_MM_Node_STRUCT;

typedef struct mm_sdram_struct {
 unsigned int    count;
 P_MM_Node_STRUCT  *next;
} MM_SDRAM_STRUCT, *P_MM_SDRAM_STRUCT;

static MM_SDRAM_STRUCT mm_fix_16_head;
static MM_SDRAM_STRUCT mm_fix_64_head;
static MM_SDRAM_STRUCT mm_fix_256_head;
static MM_SDRAM_STRUCT mm_fix_512_head;
static MM_SDRAM_STRUCT mm_fix_1024_head;
static MM_SDRAM_STRUCT mm_fix_4096_head;

static P_MM_SDRAM_STRUCT pmm_fix_16_head = &mm_fix_16_head;
static P_MM_SDRAM_STRUCT pmm_fix_64_head = &mm_fix_64_head;
static P_MM_SDRAM_STRUCT pmm_fix_256_head = &mm_fix_256_head;
static P_MM_SDRAM_STRUCT pmm_fix_512_head = &mm_fix_512_head;
static P_MM_SDRAM_STRUCT pmm_fix_1024_head = &mm_fix_1024_head;
static P_MM_SDRAM_STRUCT pmm_fix_4096_head = &mm_fix_4096_head;

static P_MM_Node_STRUCT mm_management_getnode(P_MM_SDRAM_STRUCT pmm_fix_head);
static unsigned int mm_management_node_free(P_MM_SDRAM_STRUCT pmm_fix_head, unsigned int *mm_ptr, unsigned int size);

static unsigned int *mm_management_ptr = NULL;
static unsigned int mm_management_size = 0;

/*
** free_memory_start : 开机内存初始化之后,剩余可以申请的地址的首地址
** free_memory_end   : 内存可以申请的最大地址
*/
void mm_management_init(unsigned int free_memory_start, unsigned int free_memory_end)
{
 unsigned int mm_usesize=0,offset=0,mm_offset;
 unsigned char *ptr_tmp;
 unsigned int i;
 P_MM_Node_STRUCT pmm_fix_head, pmm_fix_tmp;

 free_memory_start = (free_memory_start + 3) & (~0x3);  // Align to 4-bytes boundary
 free_memory_end   = (free_memory_end + 3) & (~0x3);   // Align to 4-bytes boundary

 do{
  //[1]判断剩余内存是否满足碎片管理所需大小
  mm_usesize = 0;
  mm_usesize += C_MM_16BYTE * C_MM_16BYTE_NUM;
  mm_usesize += C_MM_64BYTE * C_MM_64BYTE_NUM;
  mm_usesize += C_MM_256BYTE * C_MM_256BYTE_NUM;
  mm_usesize += C_MM_512BYTE * C_MM_512BYTE_NUM;
  mm_usesize += C_MM_1024BYTE * C_MM_1024BYTE_NUM;
  mm_usesize += C_MM_4096BYTE * C_MM_4096BYTE_NUM;

  if(mm_usesize+free_memory_start > free_memory_end)
  {
   printf("free memory not enough for mm management,init fail\r\n");
   break;
  }
  mm_management_ptr = (unsigned char *)malloc(mm_usesize);    //申请整块碎片管理内存大小  //如果有malloc_align函数,建议改用malloc_align申请64bit对其的内存
  if(mm_management_ptr == NULL)
  {
   printf("mm management malloc fail,init fail\r\n");
   break;
  }
  mm_management_size = mm_usesize;
  ptr_tmp = mm_management_ptr;
  memset(ptr_tmp, 0x00, mm_usesize);

  //[2]内存链表头初始化,用于存放以下步骤的子链表节点
  memset((void*)pmm_fix_16_head, 0x00, sizeof(mm_fix_16_head));
  memset((void*)pmm_fix_64_head, 0x00, sizeof(mm_fix_64_head));
  memset((void*)pmm_fix_256_head, 0x00, sizeof(mm_fix_256_head));
  memset((void*)pmm_fix_512_head, 0x00, sizeof(mm_fix_512_head));
  memset((void*)pmm_fix_1024_head, 0x00, sizeof(mm_fix_1024_head));
  memset((void*)pmm_fix_4096_head, 0x00, sizeof(mm_fix_4096_head));  
  
  //[3]申请16Bytes碎片内存存放在链表  
  mm_offset = 0;
  mm_fix_16_head.count = C_MM_16BYTE_NUM;
  pmm_fix_head = pmm_fix_16_head;
  for(i=0; i<C_MM_16BYTE_NUM; i++)
  {
   pmm_fix_tmp = (P_MM_Node_STRUCT)malloc(sizeof(MM_Node_STRUCT));
   pmm_fix_tmp->iflag = MM_STATUS_FREE;
   pmm_fix_tmp->next = NULL;
   offset = (C_MM_16BYTE * i) + mm_offset;    //计算小内存碎片在大buf里的偏移地址
   pmm_fix_tmp->mm_node = ptr_tmp + offset;
   
   pmm_fix_head->next = pmm_fix_tmp;
   pmm_fix_head = pmm_fix_tmp;
  }

  //[4]申请64Bytes碎片内存存放在链表  
  mm_offset += C_MM_16BYTE * C_MM_16BYTE_NUM;
  mm_fix_64_head.count = C_MM_64BYTE_NUM;
  pmm_fix_head = pmm_fix_64_head;
  for(i=0; i<C_MM_64BYTE_NUM; i++)
  {
   pmm_fix_tmp = (P_MM_Node_STRUCT)malloc(sizeof(MM_Node_STRUCT));
   pmm_fix_tmp->iflag = MM_STATUS_FREE;
   pmm_fix_tmp->next = NULL;
   offset = (C_MM_64BYTE * i) + mm_offset;    //计算小内存碎片在大buf里的偏移地址
   pmm_fix_tmp->mm_node = ptr_tmp + offset;
   
   pmm_fix_head->next = pmm_fix_tmp;
   pmm_fix_head = pmm_fix_tmp;
  }

  //[5]申请256Bytes碎片内存存放在链表  
  mm_offset += C_MM_64BYTE * C_MM_64BYTE_NUM;
  mm_fix_256_head.count = C_MM_256BYTE_NUM;
  pmm_fix_head = pmm_fix_256_head;
  for(i=0; i<C_MM_256BYTE_NUM; i++)
  {
   pmm_fix_tmp = (P_MM_Node_STRUCT)malloc(sizeof(MM_Node_STRUCT));
   pmm_fix_tmp->iflag = MM_STATUS_FREE;
   pmm_fix_tmp->next = NULL;
   offset = (C_MM_256BYTE * i) + mm_offset;   //计算小内存碎片在大buf里的偏移地址
   pmm_fix_tmp->mm_node = ptr_tmp + offset;
   
   pmm_fix_head->next = pmm_fix_tmp;
   pmm_fix_head = pmm_fix_tmp;
  }

  //[6]申请512Bytes碎片内存存放在链表  
  mm_offset += C_MM_256BYTE * C_MM_256BYTE_NUM;
  mm_fix_512_head.count = C_MM_512BYTE_NUM;
  pmm_fix_head = pmm_fix_512_head;
  for(i=0; i<C_MM_512BYTE_NUM; i++)
  {
   pmm_fix_tmp = (P_MM_Node_STRUCT)malloc(sizeof(MM_Node_STRUCT));
   pmm_fix_tmp->iflag = MM_STATUS_FREE;
   pmm_fix_tmp->next = NULL;
   offset = (C_MM_512BYTE * i) + mm_offset;   //计算小内存碎片在大buf里的偏移地址
   pmm_fix_tmp->mm_node = ptr_tmp + offset;
   
   pmm_fix_head->next = pmm_fix_tmp;
   pmm_fix_head = pmm_fix_tmp;
  }

  //[7]申请1024Bytes碎片内存存放在链表  
  mm_offset += C_MM_512BYTE * C_MM_512BYTE_NUM;
  mm_fix_1024_head.count = C_MM_1024BYTE_NUM;
  pmm_fix_head = pmm_fix_1024_head;
  for(i=0; i<C_MM_1024BYTE_NUM; i++)
  {
   pmm_fix_tmp = (P_MM_Node_STRUCT)malloc(sizeof(MM_Node_STRUCT));
   pmm_fix_tmp->iflag = MM_STATUS_FREE;
   pmm_fix_tmp->next = NULL;
   offset = (C_MM_1024BYTE * i) + mm_offset;   //计算小内存碎片在大buf里的偏移地址
   pmm_fix_tmp->mm_node = ptr_tmp + offset;
   
   pmm_fix_head->next = pmm_fix_tmp;
   pmm_fix_head = pmm_fix_tmp;
  }
  
  //[8]申请4096Bytes碎片内存存放在链表  
  mm_offset += C_MM_1024BYTE * C_MM_1024BYTE_NUM;
  mm_fix_4096_head.count = C_MM_4096BYTE_NUM;
  pmm_fix_head = pmm_fix_4096_head;
  for(i=0; i<C_MM_4096BYTE_NUM; i++)
  {
   pmm_fix_tmp = (P_MM_Node_STRUCT)malloc(sizeof(MM_Node_STRUCT));
   pmm_fix_tmp->iflag = MM_STATUS_FREE;
   pmm_fix_tmp->next = NULL;
   offset = (C_MM_4096BYTE * i) + mm_offset;   //计算小内存碎片在大buf里的偏移地址
   pmm_fix_tmp->mm_node = ptr_tmp + offset;
   
   pmm_fix_head->next = pmm_fix_tmp;
   pmm_fix_head = pmm_fix_tmp;
  }
 }while(0);
 
 printf("mm management init end!!!\r\n");
}

unsigned int  mm_management_malloc(unsigned int size)
{
 int status = MM_STATUS_FAIL;   //MM_STATUS_FAIL表示还没申请到碎片内存
 P_MM_Node_STRUCT  pmm_fix_node;
 unsigned int *mm_ptr = NULL;

 //获取空闲碎片节点
 do{

  //[1]判断申请内存大小是否满足要求
  if(size < 0)
  {
   status = MM_STATUS_FAIL;
   printf("mm management malloc size is error\r\n");
   return NULL;
  }

  //[2]判断大小是否小于16Byets
  if(size < C_MM_16BYTE && status == MM_STATUS_FAIL)
  {
   pmm_fix_node = mm_management_getnode(pmm_fix_16_head);
   if(pmm_fix_node != NULL)
   {
    status = MM_STATUS_OK;
    break;
   }
  }

  //[3]判断大小是否小于64Byets
  if(size < C_MM_64BYTE && status == MM_STATUS_FAIL)
  {
   pmm_fix_node = mm_management_getnode(pmm_fix_64_head);
   if(pmm_fix_node != NULL)
   {
    status = MM_STATUS_OK;
    break;
   }
  }

  //[4]判断大小是否小于256Byets
  if(size < C_MM_256BYTE && status == MM_STATUS_FAIL)
  {
   pmm_fix_node = mm_management_getnode(pmm_fix_256_head);
   if(pmm_fix_node != NULL)
   {
    status = MM_STATUS_OK;
    break;
   }
  }

  //[5]判断大小是否小于512Byets
  if(size < C_MM_512BYTE && status == MM_STATUS_FAIL)
  {
   pmm_fix_node = mm_management_getnode(pmm_fix_512_head);
   if(pmm_fix_node != NULL)
   {
    status = MM_STATUS_OK;
    break;
   }
  }

  //[6]判断大小是否小于1024Byets
  if(size < C_MM_1024BYTE && status == MM_STATUS_FAIL)
  {
   pmm_fix_node = mm_management_getnode(pmm_fix_1024_head);
   if(pmm_fix_node != NULL)
   {
    status = MM_STATUS_OK;
    break;
   }
  }

  //[7]判断大小是否小于4096Byets
  if(size < C_MM_4096BYTE && status == MM_STATUS_FAIL)
  {
   pmm_fix_node = mm_management_getnode(pmm_fix_4096_head);
   if(pmm_fix_node != NULL)
   {
    status = MM_STATUS_OK;
    break;
   }
  }
 }while(0);

 if(status == MM_STATUS_OK)

 {
  mm_ptr = pmm_fix_node->mm_node;
  pmm_fix_node->iflag = MM_STATUS_BUSY;
 }
 else
 {
  mm_ptr = (unsigned int *)malloc(size);
 }

 return (unsigned int *)mm_ptr;
}

void mm_management_free(void *mm_ptr)
{
 unsigned int i;
 int status = MM_STATUS_FAIL;
 P_MM_Node_STRUCT  pmm_fix_node;

 do{
  //[1]如果地址是16Bytes碎片地址,则释放内存
  status = mm_management_node_free(pmm_fix_16_head, mm_ptr, C_MM_16BYTE);
  if(status == MM_STATUS_OK)
   break;

  //[2]如果地址是64Bytes碎片地址,则释放内存
  status = mm_management_node_free(pmm_fix_64_head, mm_ptr, C_MM_64BYTE);
  if(status == MM_STATUS_OK)
   break;

  //[1]如果地址是256Bytes碎片地址,则释放内存
  status = mm_management_node_free(pmm_fix_256_head, mm_ptr, C_MM_256BYTE);
  if(status == MM_STATUS_OK)
   break;

  //[1]如果地址是512Bytes碎片地址,则释放内存
  status = mm_management_node_free(pmm_fix_512_head, mm_ptr, C_MM_512BYTE);
  if(status == MM_STATUS_OK)
   break;

  //[1]如果地址是1024Bytes碎片地址,则释放内存
  status = mm_management_node_free(pmm_fix_1024_head, mm_ptr, C_MM_1024BYTE);
  if(status == MM_STATUS_OK)
   break;

  //[1]如果地址是4096Bytes碎片地址,则释放内存
  status = mm_management_node_free(pmm_fix_4096_head, mm_ptr, C_MM_4096BYTE);
  if(status == MM_STATUS_OK)
   break;
 }while(0);

 if(status == MM_STATUS_OK)
 {
  //do nothing,在mm_management_node_free函数中已经将pmm_fix_node->iflag设为MM_STATUS_FREE
 }
 else
 {
  free(mm_ptr);
 }
}

//获取MM_SDRAM_STRUCT里的空闲节点
static P_MM_Node_STRUCT mm_management_getnode(P_MM_SDRAM_STRUCT pmm_fix_head)
{
 P_MM_SDRAM_STRUCT pmm_fix_head_tmp = pmm_fix_head;
 P_MM_Node_STRUCT  pmm_fix_node = pmm_fix_head_tmp->next;
 unsigned int count = pmm_fix_head_tmp->count;
 unsigned int i;

 for(i=0; i<count; i++)
 {
  if(pmm_fix_node->iflag == MM_STATUS_FREE)
   break;
  pmm_fix_node = pmm_fix_node->next;
 }

 if(i < count)
  return pmm_fix_node;
 else
  return NULL;
}

//比较MM_SDRAM_STRUCT的所有节点,如果地址一致,则释放地址
static unsigned int mm_management_node_free(P_MM_SDRAM_STRUCT pmm_fix_head, unsigned int *mm_ptr, unsigned int size)
{
 P_MM_SDRAM_STRUCT pmm_fix_head_tmp = pmm_fix_head;
 P_MM_Node_STRUCT  pmm_fix_node = pmm_fix_head_tmp->next;
 unsigned int count = pmm_fix_head_tmp->count;
 unsigned int i;

 for(i=0; i<count; i++)
 {
  if(pmm_fix_node->mm_node == mm_ptr)
  {
   if(pmm_fix_node->iflag == MM_STATUS_FREE)
   {
    printf("mm management have been free\r\n");
   }
   else
   {
    pmm_fix_node->iflag = MM_STATUS_FREE;
    memset((void *)mm_ptr, 0x00, size);    //释放内存后,把碎片内存清0
   }
   
   return MM_STATUS_OK;
  }
  pmm_fix_node = pmm_fix_node->next;
 }

 return MM_STATUS_FAIL;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.
  • 210.
  • 211.
  • 212.
  • 213.
  • 214.
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220.
  • 221.
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
  • 227.
  • 228.
  • 229.
  • 230.
  • 231.
  • 232.
  • 233.
  • 234.
  • 235.
  • 236.
  • 237.
  • 238.
  • 239.
  • 240.
  • 241.
  • 242.
  • 243.
  • 244.
  • 245.
  • 246.
  • 247.
  • 248.
  • 249.
  • 250.
  • 251.
  • 252.
  • 253.
  • 254.
  • 255.
  • 256.
  • 257.
  • 258.
  • 259.
  • 260.
  • 261.
  • 262.
  • 263.
  • 264.
  • 265.
  • 266.
  • 267.
  • 268.
  • 269.
  • 270.
  • 271.
  • 272.
  • 273.
  • 274.
  • 275.
  • 276.
  • 277.
  • 278.
  • 279.
  • 280.
  • 281.
  • 282.
  • 283.
  • 284.
  • 285.
  • 286.
  • 287.
  • 288.
  • 289.
  • 290.
  • 291.
  • 292.
  • 293.
  • 294.
  • 295.
  • 296.
  • 297.
  • 298.
  • 299.
  • 300.
  • 301.
  • 302.
  • 303.
  • 304.
  • 305.
  • 306.
  • 307.
  • 308.
  • 309.
  • 310.
  • 311.
  • 312.
  • 313.
  • 314.
  • 315.
  • 316.
  • 317.
  • 318.
  • 319.
  • 320.
  • 321.
  • 322.
  • 323.
  • 324.
  • 325.
  • 326.
  • 327.
  • 328.
  • 329.
  • 330.
  • 331.
  • 332.
  • 333.
  • 334.
  • 335.
  • 336.
  • 337.
  • 338.
  • 339.
  • 340.
  • 341.
  • 342.
  • 343.
  • 344.
  • 345.
  • 346.
  • 347.
  • 348.
  • 349.
  • 350.
  • 351.
  • 352.
  • 353.
  • 354.
  • 355.
  • 356.
  • 357.
  • 358.
  • 359.
  • 360.
  • 361.
  • 362.
  • 363.
  • 364.
  • 365.
  • 366.
  • 367.
  • 368.
  • 369.
  • 370.
  • 371.
  • 372.
  • 373.
  • 374.
  • 375.
  • 376.
  • 377.
  • 378.
  • 379.
  • 380.
  • 381.
  • 382.
  • 383.
  • 384.
  • 385.
  • 386.
  • 387.
  • 388.
  • 389.
  • 390.
  • 391.
  • 392.
  • 393.
  • 394.
  • 395.
  • 396.
  • 397.
  • 398.
  • 399.

    这份代码我写得还是比较简单,注释些也写得清楚,明白它的原理,应该很容易就看懂。

    说一下这个机制的优缺点:

优点:

    小内存申请的时候,先去提前申请好的内存中获取,这样可以很好地解决内存碎片问题。

缺点以及优化:

    1.碎片管理机制可申请的碎片数量是有限的,当数量被申请完之后,还是得重新用malloc申请;但是这可以通过我定义的 C_MM_16BYTE_NUM 和 C_MM_16BYTE 这些宏定义修改碎片数量,根据项目需要修改数量,也是能很好的优化此问题;

    2.比如我要申请4个Bytes,但此时,16,64,256,512,1024这几个链表已经用完了,那此时它会用4096这个链表去给4Bytes使用,当然,这同样可以修改C_MM_16BYTE_NUM 和 C_MM_16BYTE 这些宏定义优化这个问题。

三、嵌入式内存管理2

任何程序运行起来都需要分配内存空间存放该进程的资源信息的,C程序也不例外。C程序中的变量、常量、函数、代码等等的信息所存放的区域都有所不同,不同的区域又有不同的特性。C语言学习者、尤其是在学习嵌入式的朋友,这些知识点一定要吃透!

被欺骗的C进程

    每一个C语言的程序被执行起来的时候系统为了更方便开发人员操作,会给每一个进程分配一个虚拟的内存空间,它实际上是从处理内存映射出来的。虚拟内存的起始地址结束地址都是固定的,因此虚拟内存的布局都是一样。比如有三个进程 P1 P2 P3 ,他们虽然得到的物理内存是完全不一样,但是从进程的角度来看他们三个得到的内存确实一模一样的。

c语言基础4~内存2_#define_07

   假设你正在使用的计算机实际物理内存只有 1GB 大小,而当前系统运行了三个进程,Linux 会将 PM 中的某些内存映射为三个大小均为 4GB 的虚拟内存 ,让每个进程都以为自己独自拥有了完整的内存空间,这样极大地方 便了应用层程序的数据和代码的组织。

虚拟内存布局:

    虚拟内存布局分为内核空间、栈、堆、数据段、代码段和一个不允许访问的空间(相当于一堵墙)。

c语言基础4~内存2_c语言_08

  一个用户进程可以访问的内存区域介于 0x0804 8000 到0xc0000000 之间,这个“广袤”的区域又被分成了几个部分,分别用来存放进程的代码和数据。

    下面让我们更进一步地研究虚拟内存中每一个空间所存放的是什么类型的数据。

栈内存

    栈内存是用于存放环境变量、命令行参数和局部变量的。栈内存空间十分有限,默认情况下栈的大小为 8M ,在嵌入式开发的时候我们应该尽可能减少使用栈空间。栈空间的增长,从上(高地址) 往下 (低地址)每当有一个函数被调用的时候,栈就会从上往下分配一个段,这一段空间就是一个栈帧,该内存空间用来存放该函数的局部变量。 

c语言基础4~内存2_内存碎片_09

当一个函数退出(调用结束)的时候,栈空间会从下往上释放一个栈帧,将所有的内存归还给系统。

注意:

    栈空间中的内存存放的数据值是未知的, 因此每一个局部变量在使用之前最好做好初始化

    栈内存的空间我们无法手动实现申请与释放,都是由系统自动完成,我们无法干预。

堆空间

    堆空间是相对自由的空间,这是一个非常重要的区域,因为在此区域定义的内存的 生命周期我们是可以控制的:从 malloc( )/calloc( )/realloc( )开始,到 free( )结束,其分配和释放完全由我们开发者自定义,这就给了我们最大的自由和灵活性,让程序在运行的过 程当中,以最大的效益使用内存。

c语言基础4~内存2_c语言_10

 

注意:

  • 相对于栈空间来说,堆的内存空间相对大很多
  • 堆空间的增长方式,从下(低地址)往上(高地址)
  • 堆空间中的内存都属于匿名空间, 因此需要借助指针来访问
  • 有开发者自行申请和释放的,如果没有释放那么这个空间将一直存在,直到程序结束。

数据段

    数据段中存放着全局变量、静态变量、和常量这些数据,生命周期与程序一致。程序不止,数据不断(段)。

c语言基础4~内存2_内存碎片_11

代码段

    代码段中又分成了两个空间:

    .text段:存放用户的代码(mian func ...)

    init段:当程序运行之初的一些初始化的工作(由编译器根据系统来对应添加的)

c语言基础4~内存2_c语言_12

 内存管理是嵌入式学习的重点知识,也是判断一个人是否入门的重要标志。内存管理学得好,对C语言的理解又会更加深刻一些。 

四、malloc(0)会发生什么

这个问题看起来十分刁钻,不过稍有常识的人都知道,制定 C 标准的那帮语言律师也不是吃白饭的,对这种奇奇怪怪的问题一定会有定义。翻阅C17 标准 草案 N2176,在 7.22.3 节里,有如下说法:

The order and contiguity of storage allocated by successive calls to the aligned_alloc, calloc, malloc, and realloc functions is unspecified. The pointer returned if the allocation succeeds is suitably aligned so that it may be assigned to a pointer to any type of object with a fundamental alignment requirement and then used to access such an object or an array of such objects in the space allocated (until the space is explicitly deallocated). The lifetime of an allocated object extends from the allocation until the deallocation. Each such allocation shall yield a pointer to an object disjoint from any other object. The pointer returned points to the start (lowest byte address) of the allocated space. If the space cannot be allocated, a null pointer is returned. If the size of the space requested is zero, the behavior is implementation-defined: either a null pointer is returned to indicate an error, or the behavior is as if the size were some nonzero value, except that the returned pointer shall not be used to access an object.

在这里,标准委员会明确规定了:当 malloc 接到的参数为 0 时,其行为是由实现定义的(implementation-defined)。

由实现定义的行为这个词就提醒我们,在实际编程时如果要考虑到程序在多个运行环境下进行运行时,不能对 malloc 返回的数值进行任何假设。

换言之,没事儿不要吃饱了撑的在实际编程中写下 malloc(0) 这种天怒人怨的代码。

但是,这个无意义的问题吸引了我的兴趣。因此我开始查阅 glibc 的源代码,依此了解在 glibc 下,mallloc(0) 的行为。在 glibc2.27/malloc/malloc.c 中,有如下注释:

/*
  malloc(size_t n)
  Returns a pointer to a newly allocated chunk of at least n bytes, or null
  if no space is available. Additionally, on failure, errno is
  set to ENOMEM on ANSI C systems.

  If n is zero, malloc returns a minumum-sized chunk. (The minimum
  size is 16 bytes on most 32bit systems, and 24 or 32 bytes on 64bit
  systems.)  On most systems, size_t is an unsigned type, so calls
  with negative arguments are interpreted as requests for huge amounts
  of space, which will often fail. The maximum supported value of n
  differs across systems, but is in all cases less than the maximum
  representable value of a size_t.
*/
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

注释已经说的很清楚了,当我们执行 malloc(0) 时,我们实际会拿到一个指向一小块内存的指针,这个指针指向的(分配给我们的)内存的大小是由机器决定的。

细读代码,可以发现,将读入的内存大小进行转换是由宏 checked_request2size 实现的。

相关的宏定义如下:

/* pad request bytes into a usable size -- internal version */
#define request2size(req)                                         \
  (((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE)  ?             \
   MINSIZE :                                                      \
   ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)

/* Same, except also perform an argument and result check.  First, we check
   that the padding done by request2size didn't result in an integer
   overflow.  Then we check (using REQUEST_OUT_OF_RANGE) that the resulting
   size isn't so large that a later alignment would lead to another integer
   overflow.  */

#define checked_request2size(req, sz) \
({        \
  (sz) = request2size (req);     \
  if (((sz) < (req))      \
      || REQUEST_OUT_OF_RANGE (sz)) \
    {        \
      __set_errno (ENOMEM);     \
      return 0;       \
    }        \
})
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.

也就是说,我们能申请到的数值最小为 MINSIZE ,这个 MINSIZE 的相关定义如下:

/* The smallest possible chunk */
#define MIN_CHUNK_SIZE        (offsetof(struct malloc_chunk, fd_nextsize))
/* The smallest size we can malloc is an aligned minimal chunk */
#define MINSIZE  \
  (unsigned long)(((MIN_CHUNK_SIZE+MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK))/* The corresponding bit mask value.  */
#define MALLOC_ALIGN_MASK (MALLOC_ALIGNMENT - 1)
/* MALLOC_ALIGNMENT is the minimum alignment for malloc'ed chunks.  It
   must be a power of two at least 2 * SIZE_SZ, even on machines for
   which smaller alignments would suffice. It may be defined as larger
   than this though. Note however that code and data structures are
   optimized for the case of 8-byte alignment.  */
#define MALLOC_ALIGNMENT (2 * SIZE_SZ < __alignof__ (long double) \
     ? __alignof__ (long double) : 2 * SIZE_SZ)

#ifndef INTERNAL_SIZE_T
# define INTERNAL_SIZE_T size_t
#endif

/* The corresponding word size.  */
#define SIZE_SZ (sizeof (INTERNAL_SIZE_T))

/*
  This struct declaration is misleading (but accurate and necessary).
  It declares a "view" into memory allowing access to necessary
  fields at known offsets from a given base. See explanation below.
*/

struct malloc_chunk {

  INTERNAL_SIZE_T      mchunk_prev_size;  /* Size of previous chunk (if free).  */
  INTERNAL_SIZE_T      mchunk_size;       /* Size in bytes, including overhead. */

  struct malloc_chunk* fd;         /* double links -- used only if free. */
  struct malloc_chunk* bk;

  /* Only used for large blocks: pointer to next larger size.  */
  struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
  struct malloc_chunk* bk_nextsize;
};

// GCC 提供
/* Offset of member MEMBER in a struct of type TYPE. */
#define offsetof(TYPE, MEMBER) __builtin_offsetof (TYPE, MEMBER)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.

至此,我们就可以根据这些计算出使用 glibc 在我们的电脑上运行时 malloc 出的最小空间的大小了。计算完后,还可以根据 malloc_usable_size 判断自己的计算是否正确,样例代码如下:

#include <stdio.h>
#include <malloc.h>
int main(void) {
    char *p = malloc(0);
    printf("Address: 0x%x.\nLength: %ld.\n",p,malloc_usable_size(p));
    return 0;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

该样例在我电脑内输出的结果为 24。

因此,我们知道了,在 glibc 下,执行 malloc 会得到一个指向分配给我们的大小为 24 字节的内存空间的指针。

但这只是在 glibc 下的结果,在其他 C 标准库实现内,可能你会得到一个空指针。因为标准中提到了,对于 malloc(0) 这种故意挑事的代码,实现时可以返回一个空指针作为回礼。