内存分配

 

内存分配

 内存分配的基本概念

数据保存

 

  (1) 寄存器。这是最快的保存区域,因为它位于和其他所有保存方式不同的地方:处理器内部。然而,寄存器的数量十分有限,所以寄存器是根据需要由编译器分配。我们对此没有直接的控制权,也不可能在自己的程序里找到寄存器存在的任何踪迹。

 

  (2) 堆栈。驻留于常规RAM(随机访问存储器)区域,但可通过它的堆栈指针获得处理的直接支持。堆栈指针若向下移,会创建新的内存;若向上移,则会释放那 些内存。这是一种特别快、特别有效的数据保存方式,仅次于寄存器。

 

  (3) 堆。一种常规用途的内存池(也在RAM区域),内存堆Heap)最吸引人的地方在于编译器不必知道要 从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长的时间。因此,用堆保存数据时会得到更大的灵活性。要求创建一个对象时,只需用new命令 编制相关的代码即可。执行这些代码时,会在堆里自动进行数据的保存。当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时会花掉更长的时 间!

 

  (4) 静态存储。这儿的静态Static)是指位于固定位置(尽管也在RAM里)。程序运行期间,静态存储的数据将随时等候调用。可用static关键字指出一个对象的特定元素是静态的。  (5) 常数存储。常数值通常直接置于程序代码内部。这样做是安全的,因为它们永远都不会改变。有的常数需要严格地保护,所以可考虑将它们置入只读存储器(ROM)。

(6) RAM存储。若数据完全独立于一个程序之外,则程序不运行时仍可存在,并在程序的控制范围之外。其中两个最主要的例子便是流式对象固定对象。 对于流式对象,对象会变成字节流,通常会发给另一台机器。而对于固定对象,对象保存在磁盘中。即使程序中止运行,它们仍可保持自己的状态不变。对于这些类 型的数据存储,一个特别有用的技巧就是它们能存在于其他媒体中。一旦需要,甚至能将它们恢复成普通的、基于RAM的对象。

 

 内存分配方式

一是:从静态存储区域分配,这部分内存在程序编译的时候 就分配完成,存在于程序的整个运行过程中,例如全局变量

二是:在栈上创建,函数执行时候,函数的局部存储单元吧  都在栈上创建,函数执行结束时候,这些存储单元被自动释放,栈内存分配算法内置于处理机指令集中,效率相当高,但是容量有限,一般最大1M(相对于32位系统)。

三是:在堆内存上面创建,在堆内存上面分配内存也就是我们常说的动态内存分配,利用malloc创建任意大小(32位的系统堆内存大小一般为4G)的内存空间,用完之后,再利用free将其释放,这里的创建和释放都是需要我们程序员自己来完成的,动态内存分配方便、灵活,但是问题也不少(如果你操作的不好),而且效率也远远低于栈内存。

 

堆和栈的区别

 

管理方式:

 

对于栈来说,都有编译器自动管理,无需我们程序员手工控制;而对于堆来说,内存空间的创建和释放都是由我们程序员自己手工控制的,容易memory  leak

空间大小不同

对于栈来说,一般编译器默认的栈空间大小都为1M,当然这个大小可以调节。而对于堆来说,不夸张地说有无限大,对于32位的操作系统来说,堆内存空间足有4G,要是再算上虚拟的,那就更多了,这么大对于一个程序所需的内存来说是足够大的了。

 

碎片问题:

堆内存容易产生内存碎片,而栈内存则不会,比如我们连续的分配5M的堆内存,分配了三个。接下来在程序的运行中我们又释放了中间的5M堆内存,那么接下来在分配堆内存时候,只要是大于5M,那么刚才释放的那块堆内存就不再会被用上。而栈之所以不会出现这种问题,是因为栈是一个有规则的结构,有严格的进栈和出栈的规定,因此不会出现中间空间却先被释放的情况。

 

生长方向不同:

所谓生长方向就是说分配的内存是向着地址增长的方向,还是向着地址减小的方向。堆内是

属于前者,而栈内存是属于后者的。

 

               

 

分配方式不同:

 堆都是动态分配的,没有静态分配的堆内存;而栈则不同,它既有动态分配的,也有静态

分配的。也许有人会问栈也有动态分配的?是的,有,但不常用。我们了解一下就行了,还

有栈的动态分配和堆的动态分配是不同的,栈动态分配后的内存由编译器自己释放,而堆是

由程序员释放。

 

 

分配效率:

 我们前面也讲过,栈内存的分配运算是由计算机底层支持的,具有专门的指令来处理栈的

问题,这就决定了栈的高效率。而堆不同,堆是由编程语言的函数库提供的,实现机制相当

复杂,效率远远低于栈。

 

 

 常见的内存错误

内存错误一:内存分配没有成功,却使用了它

解决办法:在使用之前检查是否为空

 

                 

内存错误二:内存虽然分配成功了,但是尚未初始化就用了,犯这个错误的原因在于一方面

没有初始化的意识,另外误以为系统会将其初始化为0,其实默认缺省值是什么,没有标准。

 解决办法:记住初始化,不要嫌麻烦

 

              

内存错误三:内存分配成功,并且也初始化了,但是操作越界了

解决办法:这个问题,我看只有我们细心避免了

 

内存错误四:忘记释放内存,造成内存泄露

解决办法:记住一旦分配堆内存就一定记得释放

内存错误五:释放了的内存,却还使用,这个问题主要是因为没有即使的将指针赋空而导致

野指针的产生。还有指针指向的是子函数局部的栈内存,这部分内存,子函数结束时候就被

销毁了。

解决办法:内存一旦释放,立刻将指针赋NULL,函数不能返回栈内存的指针。

 

内存分配规则

【规则一】 用malloc申请堆内存空间后,立即检查是否为空,以防止引用指针值为空的内存

【规则二】 不要忘记为数组和动态分配的内存空间赋初值,防止没有被初始化的内存做了右值

【规则三】 避免数组或者指针的下标越界,特别注意多1和少1的操作

【规则四】 动态内存的申请和释放务必要配对,以防止内存的泄露。

【规则五】 利用free释放堆内存之后,立即将指针赋NULL,以防止野指针的产生。

 

 

9.2 内存分配函数

这里主要讲述的堆内存的动态分配和释放,这些都是需要我们程序员自己动手完成的,功能强大的同时,也带来了很多容易犯的错误,所以大家在使用动态分配内存的时候,需要特别谨慎注意。

malloc函数

malloc函数的实质体现在,它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。调用malloc函数时,它沿连接表寻找一个大到足以满足用 户请求所需要的内存块。然后,将该内存块一分为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。接下来,将分配给用户的那块内存传给 用户,并将剩下的那块(如果有的话)返回到连接表上。调用free函数时,它将用户释放的内存块连接到空闲链上。到最后,空闲链会被切成很多的小内存片 段,如果这时用户申请一个大的内存片段,那么空闲链上可能没有可以满足用户要求的片段了。于是,malloc函数请求延时,并开始在空闲链上翻箱倒柜地检 查各内存片段,对它们进行整理,将相邻的小空闲块合并成较大的内存块。如果无法获得符合要求的内存块,malloc函数会返回NULL指针,因此在调用 malloc动态申请内存块时,一定要进行返回值的判断。

 

一般形式:

指针变量 = (强制转换)malloc(字节数);

如:int* p = (int *) malloc ( sizeof(int) * 100 );

当然也可以写成:int *p;

                P =  (int *) malloc ( sizeof(int) * 100 );

注意:在C语言中使用malloc时,必须导入malloc.h头文件。

 

calloc函数

 

calloc是一个C语言函数

 

函数名: calloc

 

功 能: 在内存的动态存储区中分配n个长度为size的连续空间,函数返回一个指向分配起始地址的指针;如果分配不成功,返回NULL

 

malloc的区别:

  calloc在动态分配完内存后,自动初始化该内存空间为零,而malloc不初始化,里边数据是随机的垃圾数据。

一般形式:指针变量 = (强制转换)malloc(单位,字节数);

例如:

char *str;

str = (char*)calloc(10, sizeof(char));

注意使用时需加头文件:stdlib.hmalloc.h

 

realloc函数

 

   原型extern void *realloc(void *mem_address, unsigned int newsize);

 

  语法:指针名=(数据类型*realloc(要改变内存大小的指针名,新的大小)。

  头文件#include <stdlib.h

> 有些编译器需要#include <alloc.h>,在TC2.0中可以使用alloc.h头文件

 

  功能:先按照newsize指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来mem_address所指内存区域,同时返回新分配的内存区域的首地址。即重新分配存储器块的地址。

 

  返回值:如果重新分配成功则返回指向被分配内存的指针,否则返回空指针NULL。 

注意:这里原始内存中的数据还是保持不变的。当内存不再使用时,应使用free()函数将内存块释放。

例如:

int *pn=(int *)malloc(5*sizeof(int));

……

…….

pn=(int *)realloc(pn,10*sizeof(int));

 

free函数

分配的堆内存,使用结束后一定要释放,而且释放后还必须将其赋空,防止野指针的产生。

 

原型: void free(void *ptr)

 

  功 能: 释放已分配的块

 

  程序例:

 

  #include <string.h>

 

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

 

  int main(void)

 

  {

 

  char *str;

 

  /* allocate memory for string */

 

  str = (char *)malloc(10);

 

  /* copy "Hello" to string */

 

  strcpy(str, "Hello");

 

  /* display string */

 

  printf("String is %s\n", str);

 

  /* free memory */

free(str);

str = NULL;

 

  return 0;

}

 

 内存分配实例

大家是不是感觉长篇的理论看着头痛了,那么下面我们要说的知识点:指针参数如何传递内

 

存的?我们就不说许多了,我们来通过小例子来探讨.

 

 

 

Analysis

我们前面见过类似的问题,p传入函数getMomery()后,会生成一个副本p1,我们分配的内存空间给了p1,p却没有。所以p任然是NULL,所以结果段错误。

 

 

 

 

 

Analysis

我们还像上面一样分析,&p传入函数getMomery()后,会复制一个副本,我们称之为AA = &p;那么我们改变*A自然就是在改变p,所以我们给*A分配了内存空间,那么p也就分得了内存空间,其实还有一种方法也可以达到这种效果,就是利用返回值,如果你感兴趣希望你自己实现以下,很多人都是从返回值的方法,慢慢过度到二级指针的。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章