温故C语言内存管理

1. 内存管理简介

在计算机系统,特别是嵌入式系统中,内存资源是非常 有限的。尤其对于移动端开发者来说,硬件资源的限制使得其在程序设计中首要考虑的问题就是如何 有效地管理内存资源。

常见内存使用错误:

  • 内存申请未成功,就进行使用
  • 内存申请成功,但没有初始化
  • 内存初始化成功,但越界访问
  • 忘记释放内存或者释放一部分

内存管理不当的危害?

  • 没有初始化,会造成内存出错
  • 越界访问内存可能导致崩溃
  • 忘记释放内存造成内存泄露

C语言的内存管理:

C语言为用户提供了相应内存管理的AP接口,如 malloc()free()new()等函数,需要开发者手动管理。而javaC#则有自动内存回收机制,基本无需再对内存进行操作了。

2. 内存分类 栈区(stack)

由系统自动分配

堆区(heap)

在程序的执行过程中才能分配,由程序员决定

全局区(静态区)

静态区存放程序中所有的全局变量和静态变量

常量区

常量字符串就是放在这里的

代码段:

代码段(code segment/text segment)。通常是指用来存放程序执行代码的一块內存区域。代码区的指令中包括操作码和要操作的对象(或对象地址引用)。如果是立即数(即具体的数值,如5)直接包含在代码中;如果是局部数据,将在栈区分配空间,然后引用该数据地址。

数据段:

数据段(data segment)通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。

BSS段:

BSS段(Block Started by Symbol)。指用来存放程序中未初始化的全局变量的一块内存区域。
BSS段本质上也属于数据段,都用来存放C程序中的全局变量。区别在于.data段中存放初始化为非零的全局变量,而把显式初始化为0或者并未显式初始化(C语言规定未显式初始化的全局变量值默认为0)
的全局变量存在BSS段。

3. 栈区(stack)

由编译器 自动分配释放,存放函数的参数值、局部变量的值等,是一种先进后出的内存结构。

哪些是分配在栈空间?

  • 局部变量的值存放在栈上
  • 在函数体中定义的变量通常是在栈上

函数栈分配:

在函数调用时,第一个进栈的是主函数中函数调用后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。

栈内存什么时候回收?

栈内存的分配和释放也由编译器在函数进入和退出时插入指令自动完成,生命周期和函数、局部变量一样。

栈空间的大小:

在 Windows下,栈是向低地址扩展的数据结构,是块连续的内存的区域。栈空间一般较小,栈大小与编译器有关。默认情况下,visual studio 2010的栈大小为1M。但在平时应用程序中,由于函数会使用栈结果,所以只能用略小于1M大小的栈如果申请的空间超过栈的剩余空间时,将提示Stack overflow。

示例代码:

#include<stdio.h>

struct  A
{};

class  B
{};

void   fun(int  a  , int  b) //参数a,b在栈上, 在函数体结束的时候,栈内存释放
{
   int   c;//局部变量,在栈上, 在函数体结束的时候,栈内存释放
}

int  main()
{
   int  a = 10;//局部变量在栈上,  在main函数结束的时候,栈内存释放

   char  b[] = "hello";//数组变量也在栈上,  在main函数结束的时候,栈内存释放

   char  *c = NULL;//在栈上,  在main函数结束的时候,栈内存释放

   {
       A   d;//结构体变量,  在栈上
       B   e;//类对象在栈上
   } //d,e 在离开这个{}时,栈内存销毁释放

   //测试栈的大小
   //char   buf[1024 * 1024] = { 'A' };//1M时崩溃了
   char   buf[1000* 1024] = { 'A' };//栈空间略小于1M

   //经过编译期设置为5M之后,栈空间变大了
   char   buf[49 * 1024 * 1024 / 10] = { 'A' };//栈空间略小于5M

   printf("%d" , sizeof(buf)  );

   return 0;
}

4. 堆区(heap)

需程序员自己申请,并可在运行时指定空间大小,并由程序员手动进行释放,容易产生 memory leak

哪些是分配在堆空间?

调用 mallocrealloccalloc函数

//分配得来得10*4字节的区域在堆区
p1 = (char*)malloc(10*sizeof(int));

堆空间需要手动释放:

堆是由 malloc()等函数分配的内存块,内存释放由程序员调用free()函数手动释放

堆空间的大小:

堆空间一般较大,与64位/32位,编译器有关,受限于计算机系统中有效的虚拟内存;理论上32位系统堆内存可以达到4G的空间,实际上2G以内,64位128G以内(虚拟内存16TB)

示例代码:

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

int  main()
{
   //手动分配、这里就是分配了堆内存
   int  *p = (int*)malloc(10 *  sizeof(int ));

   //手动释放
   free(p);

   int MB = 0;
   while (malloc(1024 * 1024))//每次分配1M
   {
       MB++;
   }
   printf("分配了 %d MB \n", MB); 

   return 0;
}

栈与堆的区别:

类型 分配释放 大小 是否连续 申请效率
栈区 由编译器自动分配释放 较小 一块连续的内存区域 由系统自动分配,速度快
堆区 由程序员分配释放 较大 堆是向高地址扩展的数据结构,是不连续的内存区域 速度慢,容易产生内存碎片

5. 全局区(静态区)

全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在块区域。

哪些是分配在全局静态区?

  • 全局变量
  • static静态变量

全局静态区何时释放?

全局变量、静态变量在整个程序运行的生存期都存在,所以在程序结束时才释放

示例代码:

#include<stdio.h>

//储存在全局静态区
int   a;  				//全局变量,未初始化
short   b = 10;			//全局变量,已赋值
char  *c = NULL;		//全局变量,已赋值
static  int   f = 200;	//静态变量

int  main()
{
   static  int  d = 100;
   static  int  e = 200;

   printf("%p\n", &a);
   printf("%p\n", &b);
   printf("%p\n", &c);
   printf("%p\n", &d);
   printf("%p\n", &e);
   printf("%p\n", &f);
}

6. 常量区

字符串常量是放在常量区,当你初始化赋值的时候,这些常量就先在常量区开辟一段空间,保存此常量,以后相同的常量就都使用一个地址。

示例代码:

#include<stdio.h>

//“AAA”是字符串常量,存放在常量区
char  *p = "AAA";

int  main()
{
   //p1是局部变量,在栈上, “AAA”是字符串常量,存放在常量区
   char  *p1 = "AAA";

   //p2是局部变量,在栈上,“AAA”不是字符串常量,她只是一种初始化的写法
   char  p2[]= "AAA";

   //p3是局部变量,在栈上, “AAA”是字符串常量,存放在常量区
   char  *p3 = "AAA";

   //p4是局部变量,在栈上, “AAB”是字符串常量,存放在常量区
   char  *p4 = "AAB";

   printf("%p\n", p);
   printf("%p\n", p1);
   printf("%p\n", p2);
   printf("%p\n", p3);
   printf("%p\n", p4);
}

7. malloc、calloc、realloc函数

三个函数的作用?

它们都能分配堆內存、成功返回内存的首地址,失败就返回NULL

malloc函数:

void *malloc(
  size_t size
);

该函数将在堆上分配一个 size byte大小的内存。不对内存进行初始化,所以新内存其值将是随机的。

calloc函数:

void *calloc(
  size_t number,
  size_t size
);

该函数功能与 malloc相同,它将分配countsize大小的内存,自动初始化该内存空间为零。

realloc函数:

void *realloc(
  void *memblock,
  size_t size
);

该函数将ptr内存大小增大或减小到newsize

realloc函数返回的两种情况:

  • 如果当前连续内存块足够 realloc的话,只是将p1所指向的空间扩大,并返回p1的指针地址。
  • 如果当前连续内存块不够长度,再找一个足够长的地方,分配一块新的内存p2,并将p1指向的内容Copy到p2,并释放p1指向的旧内存,然后返回p2。

示例代码:

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

int  main()
{
   //malloc  ,参数是字节数 , 并且这块内存空间的值是随机的
   int  *p = (int *)malloc(5 *  sizeof(int));
   p[0] = 123;
   for (int i = 0; i < 5; ++i)
   {
   	printf("%d   ", p[i]); //后面4个值随机
   }

   printf("\n------------------------------------------------------------\n " );

   //calloc,参数两个, 自动将内存空间初始化为0
   int   *p2 = (int *)calloc(5, sizeof(int));
   p2[4] = 123;
   for (int i = 0; i < 5; ++i)
   {
   	printf("%d   ", p2[i]);
   }

   printf("\n------------------------------------------------------------\n ");

   //realloc ,可以调整内存空间的大小 ,并且拷贝原来的内容(调大,或者  缩小)
   //int  *p3 =(int *) realloc(p, 6* sizeof(int));//调大一点点,两个地址相同
   //int  *p3 = (int *)realloc(p, 2 * sizeof(int));//缩小,两个地址相同
   int  *p3 = (int *)realloc(p, 100 * sizeof(int));//调很大,两个地址不同 ,释放原来的内存空间
   for (int i = 0; i <2; ++i)
   {
   	printf("%d   ", p3[i]);
   } 

   printf("\np地址:  %p   ,  p3的地址:  %p   ", p,  p3);

   return 0;
}

8. strcpy、memcpy、memmove函数

头文件:

#include <string.h>

strcpy函数

char *strcpy(
  char *strDestination,
  const char *strSource
);

src所指由\0结束的字符串复制到dest所指的数组中。

注意事项:

srcdest所指内存区域不能重叠,且dest必须有足够的空间来容纳src的字符串,src的结尾必须是'\0',返回指向dest的指针。

memcpy函数

void *memcpy(
  void *dest,
  const void *src,
  size_t count
);

src所指内存区域复制 count个字节到dest所指内存区域。

注意事项:

函数返回指向dest的指针和 strcpy相比,memcpy不是遇到\0就结束,而一定会拷贝n个字节注意srcdest所指内存区域不能重叠,否则不能保证正确。

memmove函数

void *memmove(
  void *dest,
  const void *src,
  size_t count
);

函数功能:与 memcpy相同。

注意事项:

srcdest所指内存区域可以重叠memmove可保证拷贝结果正确,而memcpy不能保证。函数返回指向dest的指针。

memset函数

void *memset(
  void *dest,
  int c,
  size_t count
);

常用于內存空间的初始化。将已开辟内存空间s的首n个字节的值设为值c,并返回s

示例代码:

#include<stdio.h>
#include<string.h>
#include<assert.h> 

//模拟memcpy函数实现
void  *  MyMemcpy(void *dest, const void *source, size_t count)
{
   assert((NULL != dest) && (NULL != source));
   char *tmp_dest = (char *)dest;
   char *tmp_source = (char *)source;
   while (count--)//不判断是否重叠区域拷贝
   	*tmp_dest++ = *tmp_source++;
   return dest;
}

//模拟memmove函数实现
void * MyMemmove(void *dest, const void *src, size_t n)
{
   char temp[256];
   int i;
   char *d =(char*) dest;
   const char *s =(char *) src;
   for (i = 0; i < n; i++)
   	temp[i] = s[i];
   for (i = 0; i < n; i++)
   	d[i] = temp[i];
   return dest;
}

int  main()
{
    //strcpy进行字符串拷贝
   //注意:  1. src字符串必须以'\0'结束,  2. dest内存大小必须>=src
   char  a[5];
   //char  b[5] = "ABC";//字符串结尾会自动的有\0 , 此处 b[4]就是'\0'
   char  b[5];
   b[0] = 'A';
   b[1] = 'B';
   b[2] = 'C';
   b[3] = '\0';//必须加\0,否则strcpy一直向后寻找\0
   strcpy(a, b);
   printf("%s\n", a);

   //memcpy函数, 直接拷贝内存空间,指定拷贝的大小
   int   a2[5];
   int   b2[5] = { 1,2,3,4,5 };//不需要'\0'结束
   memcpy(a2, b2,   3 *sizeof(int)   );//指定拷贝的大小, 单位  字节数
   printf("%d , %d  ,%d\n" , a2[0] ,  a2[1],  a2[2]);

   MyMemcpy(a2 + 3, b2 + 3,   2 * sizeof(int));
   printf("%d , %d \n", a2[3], a2[4]);

   //演示内存重叠的情况
   char  a3[6] = "123";
   //MyMemcpy(a3 + 1, a3, 4); //得到11111
   memcpy(a3 + 1, a3, 4);//虽然它是正确的,但是不保证,重叠拷贝应该避免使用它
   printf("%s\n", a3);

   //memmove功能与memcpy一样,但是了考虑了重叠拷贝的问题,可以保证正确
   char  a4[6] = "123";
   //MyMemmove(a4 + 1, a4, 4);//可以保证正确
   memmove(a4 + 1, a4, 4);//可以保证正确
   printf("%s\n", a4);

   //memset比较简单, 把内存区域初始化化为某个值
   char a5[6];
   memset(a5, 0, 6);
   for (int i = 0; i < 6; ++i)
   {
   	printf("%d", a5[i]);
   }

   return 0;
}

9. 实现动态数组

思路:

利用 realloc函数,当数组元素满的时候,扩充内存区域,然后加入元素!

示例代码:

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

//为了代码的可读性,将设计为C++中的类,利用struct 代替

//动态数组
struct  Array
{
   //自动构造函数,它初始化
   Array()
   {
   	grow = 3;
   	size = 3;
   	n = 0;
   	//分配并初始化内存
   	pHead = (int  *)calloc(size  , sizeof(int));
   	assert(pHead != NULL);
   }

   void   AddElem(int  e)
   {
   	if (n >= size)//说明数组满了
   	{
   		//需要扩大内存
   		size += grow;
   		pHead = (int  *)realloc( pHead,   size * sizeof(int)  );
   		assert(pHead != NULL);
   	}

   	pHead[n++] = e; //添加元素
   }

   void  Print()
   {
   	printf("\n\n数组总空间:%d   ,   元素个数: %d  \n",  size,  n);
   	for (int i = 0; i < n; ++i)
   	{
   		printf("%d  " ,  pHead[i]);
   	}
   }

   int   size;//总空间, 不是固定的,可以增大的
   int   n;//当前数组的元素个数
   int   grow;//每次数组内存满了的时候,增长量

   int   *pHead;//数组的起始地址
};

int  main()
{
   Array  arr; 

   arr.AddElem(1);
   arr.AddElem(2);
   arr.AddElem(3);
   arr.AddElem(4);
   arr.AddElem(5);
   arr.AddElem(6);
   arr.AddElem(7);
   arr.AddElem(8);
   arr.Print(); 

   arr.AddElem(11);
   arr.AddElem(22);
   arr.AddElem(33);
   arr.AddElem(44);
   arr.AddElem(55);
   arr.Print();

   return  0;
}

10. 内存越界

何谓內存访问越界,简单的说,你向系统申请了一块内存,在使用这块内存的时候,超出了你申请的范围。

  • 访问到野指针指向的区域,越界访问
  • 数组下标越界访问
  • 使用已经释放的内存
  • 企图访问一段释放的栈空间
  • 容易忽略 字符串后面的'\0'

注意:

strlen所作的是一个计数器的工作,它从内存的某个位置(可以是字符串开头,中间某个位置,甚至是某个不确定的内存区域)开始扫描,直到碰到第一个字符串结束符'\0'为止,然后返回计数器值( 长度不包含'\0')。

示例代码:

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

char  * fun()
{
   char arr[10];
    return  arr;
}//arr是栈内存,离开此花括号,栈被释放回收

int main()
{
   //1.访问到野指针指向的区域,越界访问
   char  *p;//没有初始化,野指针,乱指一气
   //strcpy(p, "hello");//非法越界访问

   //2.数组下标越界访问
   int   * p2 = (int *)calloc(10, sizeof(int));
   for (size_t i = 0; i <= 10; i++)
   {
   	p2[i] = i;//很难察觉的越界访问, 下标越界
   }

   //3.使用已经释放的内存
   char *p3 = (char *)malloc(10);
   free(p3);
   if (p3 != NULL)//这里if不起作用
   {
   	strcpy(p3, "hello");//错误,p3已经被释放
   }

   //4.企图访问一段释放的栈空间
   char *p4 = fun();  //p4指向的栈空间已经被释放
   strcpy(p4, "hello");
   printf("%s\n",p4);

   //5.容易忽略 字符串后面的'\0'
   char  *p5 = (char *)malloc(strlen("hello"));//忘记加1
   strcpy(p5, "hello");//导致p5的长度不够,越界

   return 0;
}

11. 内存泄露(Memory Leak)

是指程序中己动态分配的堆內存由于某种原因程序未释放或无法释放,造成系统內存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

  • 丢失了分配的内存的首地址,导致无法释放
  • 丢失分配的内存地址
  • 企图希望传入指针变量获取对内存,殊不知是拷贝
  • 每循环一次,泄露一次内存
  • 非法访问常量区

示例代码:

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

char  * GetBuf()
{
   return  (char *)malloc(10);
}

void  GetBuf2(char *p)//p已经是一份拷贝,和原参数无任何关系
{
   p= (char *)malloc(10);
}

char  * GetBuf3()
{
   char  *p = "hello";//常量内存区,不可更改
   return p;
}

int main()
{
   //1.丢失了分配的内存的首地址,导致无法释放
   GetBuf();//忘记接收返回值了

   //2.丢失分配的内存地址
   char  *p1= (char *)malloc(10);
   char  *p2 = (char *)malloc(10);
   p1 = p2;//这一步,导致第一次分配的堆内存丢失,无法释放

   //3.企图希望传入指针变量获取对内存,殊不知是拷贝
   char  *p3 = NULL;
   GetBuf2(p3); //应该使用指针的指针,或者引用
   //strcpy(p3, "hello"); //错误,这里的p3仍然为NULL

   //4.每循环一次,泄露一次内存
   char  * p4 = NULL;
   for (int i = 0; i < 10; ++i)
   {
   	p4= (char *)malloc(10);
   }
   strcpy(p4, "hello"); // 这里的p4只指向最后一次分配的,前面的全部内存泄漏

   //5.非法访问常量区
   char *p5 = GetBuf3();
   strcpy(p5, "hello");  

   return 0;
}

12. 内存池技术简介

内存碎片:

内存碎片一般是由于空闲的內存空间比要连续申请的空间小,导致这些小内存块不能被充分的利用,当你需要分配大的连续内存时,尽管剩余内存的总和足够,但系统找不到连续的内存,所以导致分配失败malloc/free大量使用会造成内存碎片

为什么会产生内存碎片?

如果有100个单位的连续空闲内存,那么先申请5单元的连续内存,再申请50单元的内存这时释放一开始的5单元的内存。这时,如果你一直申请比5单元大的内存单元,那么开始的那连续的5单元就一直不能被使用。

内存池技术:

内存的申请、释放是低效的,我们只在开始申请一块大內存(不够继续申请),然后每次需要时都从这块内存取出,并标记这块内存是否被使用。释放时仅仅标记而不真的free,只有内存都空闲的时候,才释放给操作系统。这样减少了 mallocfree次数,从而提高效率。

13. C语言实现内存池

设计思路:

先分配几个大的连续内存块(MemoryBlock),每个内存块用链表链接起来,然后通过一个內存池结构(MemoryPool)管理!

代码实现:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>

class  MemoryBlock
{
public:
   int             nSize; //该内存块的总大小 (单元个数X每个单元大小),以字节为单位

   int             nFree;  //该内存块还有多少个空闲的单元

   int             nFirst; //当前可用空闲单元的序号,从0开始

   MemoryBlock*    pNext;  //指向下一个MemoryBlock内存块

   char            aData[1]; //用于标记分配内存开始的位置  

   //.....这个结构下面全是内存

public:
   MemoryBlock(int  unitCount, int  unitSize)
   {
   	 nSize = unitCount* unitSize;
   	 nFree = unitCount;
   	 nFirst = 0;
   	 pNext = NULL;

   	 char  *p = aData;//获取内存单元的首地址

   	 for (int i = 0; i < unitCount -1; ++i)
   	 {
   		 *((short *)p) = i + 1; //第0块的下个空闲索引是不是第1块
   		  p += unitSize;
   	 }

   	 *((short *)p) = -1;//最后一块没有下一个空闲空间了,为-1
   }

   void  *  operator new (size_t  t,  int   size)
   {
   	 int  headSize = sizeof(MemoryBlock);
         return   ::operator  new(headSize + size);
   }
};

//分配固定内存的内存池
class  MemoryPool
{

public:
   //初始大小 (每一个MemoryBlock中初始的单元个数)
   int             nInitCount; 

   //(后面增加的MemoryBlock中单元个数)
   int             nGrowSize; 

   //分配单元大小,MemoryBlock中每个单元的大小
   int             nUnitSize; 

   //内存块链表
   MemoryBlock*    pBlock;

public:
   MemoryPool( int  _nInitCount, int  _nGrowSize, int _nUnitSize)
   {
   	nInitCount = _nInitCount;
   	nGrowSize = _nGrowSize;
   	nUnitSize = _nUnitSize;
   	pBlock = NULL;
   }

   char  *  Alloc() //每次只返回 nUnitSize 大小的内存
   {
   	if (pBlock == NULL)
   	{
   		 MemoryBlock  *   p =(MemoryBlock  *) new (nInitCount * nUnitSize) MemoryBlock(nInitCount, nUnitSize);
   		 assert(p != NULL);

   		 pBlock = p;
   	}

   	MemoryBlock  *  pB = pBlock;
   	while (pB !=NULL  &&   pB->nFree==0)
   	{
   		pB = pB->pNext;
   	}

   	if (pB == NULL)//一直没找到了可以分配的MemoryBlock,说明内存池已满
   	{
   		pB = (MemoryBlock  *) new (nGrowSize  * nUnitSize) MemoryBlock(nGrowSize, nUnitSize);
   		assert(pB != NULL);

   		pB->pNext = pBlock;
   		pBlock = pB;
   	}

   	//得到第一个可用的空闲内存地址
   	char *pFree = pB->aData + pB->nFirst * nUnitSize;
   	//把nFirst值改为下一个空闲的索引 (存储在当前内存的前两个字节)
   	pB->nFirst = *((short*)pFree);
   	pB->nFree--;
   	return  pFree;
   }

   void   Free(void  *p)
   {
     //考虑这个地址落在哪个 MemoryBlock 上
   	MemoryBlock  *  pB = pBlock;
   	while (pB  != NULL  &&   p <  pB->aData   ||  p > pB->aData+  pB->nSize   )
   	{
   		pB = pB->pNext;
   	}

   	if (pB!= NULL)//找到了p所在的MemoryBlock
   	{
   		 //销毁之前先让它的前两个字节指向nFirst (当前空闲的索引)
   		*((short*)p) = pB->nFirst;

   		 //nFirst的值指向当前释放的
   		 pB->nFirst = ((char *)p - pB->aData) / nUnitSize;

   		 pB->nFree++;
   	}
   	else
   	{
   		printf("错误,此内存并非内存池分配的!\n");
   	}
   }

   void  Print()
   {
   	printf("\n\n\n");

   	MemoryBlock  *  pB = pBlock;
   	while (pB != NULL )
   	{
   		printf("\n首地址:%p   总大小:%d   空闲个数: %d   下一个空闲:%d  \n",
   			pB->aData ,  pB->nSize, pB->nFree ,pB->nFirst);

   		for (int i = 0; i < pB->nSize / nUnitSize; ++i)
   		{
   			printf("\t %d" ,  *  ((int *) ( pB->aData + i * nUnitSize )));
   		}

   		pB = pB->pNext;

   		printf("\n---------------------------------------------------------\n");
   	}
   }
};

int main()
{
   MemoryPool   pool(3, 3, 4);

   int  *p1 = (int *)pool.Alloc();
   *p1 = 111;

   int  *p2 = (int *)pool.Alloc();
   *p2 = 222;

   int  *p3 = (int *)pool.Alloc();
   *p3 = 333;

   pool.Print();

   int  *p4 = (int *)pool.Alloc();
   *p4 = 444;

   pool.Print();

   int  *p5 = (int *)pool.Alloc();
   *p5 = 555;

   pool.Print();

   pool.Free( p1);
   pool.Free(p2);
   pool.Free(p3);
   pool.Print();

    p1 = (int *)pool.Alloc();
   *p1 = 111;

    p2 = (int *)pool.Alloc();
   *p2 = 222;

    p3 = (int *)pool.Alloc();
   *p3 = 333;
   pool.Print();

   return 0;
}

到此这篇关于温故C语言内存管理的文章就介绍到这了,更多相关C语言内存管理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 模拟实现C语言中的内存管理

    这里模拟了C语言中的内存管理,当我们要创建或者使用一个对象时,那么这个对象会调用retain方法,计数+1,当我们要释放对象,我们会调用free,这里注意要对计数记性判断,如果是0的话,那么就会销毁. #import <Foundation/Foundation.h> int cnt = 0; void fun (charchar * p) { printf("%c\n",p[0]); } charchar * retain1(charchar * p) { //retai

  • 一文秒懂C语言/C++内存管理(推荐)

    C 语言内存管理指对系统内存的分配.创建.使用这一系列操作.在内存管理中,由于是操作系统内存,使用不当会造成毕竟麻烦的结果.本文将从系统内存的分配.创建出发,并且使用例子来举例说明内存管理不当会出现的情况及解决办法. 一.内存 在计算机中,每个应用程序之间的内存是相互独立的,通常情况下应用程序 A 并不能访问应用程序 B,当然一些特殊技巧可以访问,但此文并不详细进行说明.例如在计算机中,一个视频播放程序与一个浏览器程序,它们的内存并不能访问,每个程序所拥有的内存是分区进行管理的. 在计算机系统中

  • C语言中储存类别与内存管理的深入理解

    储存类别 C语言提供了多种储存类别供我们使用,并且对应的有对应的内存管理策略,在了解C中的储存类型前,我们先了解一下与储存类型相关的一些概念. 1. 基础概念 对象:不同于面向对象编程中的对象的含义,C语言是面向过程编程,不存在这样对象的概念,这个对象指的是值储存所占据物理内存空间. 左值:左值是可以指定对象的表达式,它的最简单形式即为标识符,复杂的可以为为指针之类.一个表达式成为左值的前提是它确实指定了一块作为对象的储存空间,例如: int a = 1;//a作为标识符,也作基础表达式,指定了

  • 温故C语言内存管理

    1. 内存管理简介 在计算机系统,特别是嵌入式系统中,内存资源是非常 有限的.尤其对于移动端开发者来说,硬件资源的限制使得其在程序设计中首要考虑的问题就是如何 有效地管理内存资源. 常见内存使用错误: 内存申请未成功,就进行使用 内存申请成功,但没有初始化 内存初始化成功,但越界访问 忘记释放内存或者释放一部分 内存管理不当的危害? 没有初始化,会造成内存出错 越界访问内存可能导致崩溃 忘记释放内存造成内存泄露 C语言的内存管理: C语言为用户提供了相应内存管理的AP接口,如 malloc(),

  • C语言内存管理及初始化细节示例详解

    目录 地址空间 指针与内存关系 内存分配与初始化细节 内存泄漏 Cookie 地址空间 首先我们回味一下之前的老图,这个图由于是我手残加 ppt 即时创作,又因为是C语言入门时讲的,内容非常粗糙磕碜.要仔细研究这张图我们应该将它翻转90度会更加容易理解更贴近原理: 我们所熟知的,栈区数据存储的地址是从高地址到低地址,堆区数据存储的地址则是由低到高,而堆区下面可细分为未初始化和已初始化的全局数据区,字符常量区和代码区.而细心的你可能注意到了我代码区下面留了一撮空间代表下面还有,但这一撮属于灰色地带

  • C语言与C++中内存管理详解

    目录 内存分布 动态内存管理方式-堆区 C语言动态内存管理 C++动态内存管理 new和delete的用法 operator new与operator delete函数 new和delete的实现原理 定位new表达式 高频面试题 重点new/delete和malloc/free的区别 内存泄漏 内存分布 主要段及其分布 ​ 每个程序运行起来以后,它将拥有自己独立的虚拟地址空间.这个虚拟地址空间的大小与操作系统的位数有关系.32位硬件平台的虚拟地址空间的地址可以从0~2^32-1,即0x0000

  • C语言与C++内存管理超详细分析

    目录 一.内存 1.1 内存四区 1.2 使用代码证实内存四区的底层结构 二.malloc 和 free 2.1 malloc 和 free 的使用 2.2 内存泄漏与安全使用实例与讲解 三.new 和 delete 3.1 new 和 delete 使用 3.2 delete 与 delete[] 的区别 一.内存 在计算机中,每个应用程序之间的内存是相互独立的,通常情况下应用程序 A 并不能访问应用程序 B,当然一些特殊技巧可以访问,但此文并不详细进行说明.例如在计算机中,一个视频播放程序与

  • C语言详细分析讲解内存管理malloc realloc free calloc函数的使用

    目录 C语言内存管理 一.动态空间申请 二.动态空间的扩容 三.释放内存 C语言内存管理 malloc && realloc && free && calloc c语言中为了进行动态内存管理,<stdlib.h>中提供了几个函数帮助进行内存管理. 我们知道,C语言中是没有C++中的容器或者说是python中list,set这些高级的数据结构的,我们一旦申请了一段内存空间以后这一段空间就归你了,比如我们举个例子,我们申请一个数组 int nums[

  • 一文带你搞懂C语言动态内存管理

    目录 一.malloc函数和free函数 二.calloc函数与malloc函数的异同 三.柔性数组 一.malloc函数和free函数 (1) 这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针. 如果开辟成功,则返回一个指向开辟好空间的指针. 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查. 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定. 如果参数 size为0,malloc的行为是标准是未

  • C/C++中的内存管理小结

    前言 我们最初熟知的内存开辟方式: int val = 20: 在栈空间上开辟4个字节 char array[10]: 在栈空间上开辟10个字节的连续空间 上述开辟空间的方式有两个特点: 空间开辟大小是固定的. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配. 但是对于空间的需求,不仅仅是上述的情况,有时候我们需要的空大小在程序运行时才能知道,那此时静态的开辟空间的方式就不能满足了,我们这时候只能试试动态内存开辟. 这篇博客就来带大家梳理一下C/C++中的内存管理. 一:C/C

  • 详解C/C++内存管理

    C/C++赋予程序员管理内存的自由,是C/C++语言特色,虽然这引入了复杂度和危险性,但另一方面,它也增加了控制力和灵活性,是C/C++独特之处,亦是强大之处. C/C++内存分布 让我们先来看看下面这段代码: int globalVar = 1; static int staticGlobalVar = 1; void Test() { static int staticVar = 1; int localVar = 1; int num1[10] = { 1, 2, 3, 4 }; char

  • 一起来学习C++的动态内存管理

    目录 1.new和delete 2.new和delete在底层是怎么实现的: 2.1new底层的实现: 我们先来new一个test类型的空间. 2.2delete底层的实现: 我们执行delete语句,转到反汇编来 2.3new []底层的实现: 2.4delete []的实现: 3.重载new和delete 4.定位new: 5.内存检测函数:_CrtDumpMemoryLeaks(); 总结 1.new和delete C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力而且使用起来

随机推荐