内存分配
内存分配的基本概念
数据保存
(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.h或malloc.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()后,会复制一个副本,我们称之为A,A = &p;那么我们改变*A自然就是在改变p,所以我们给*A分配了内存空间,那么p也就分得了内存空间,其实还有一种方法也可以达到这种效果,就是利用返回值,如果你感兴趣希望你自己实现以下,很多人都是从返回值的方法,慢慢过度到二级指针的。