一:C语言中的内存机制ios
在C语言中,内存主要分为以下5个存储区:c++
(1)栈(Stack):位于函数内的局部变量(包括函数实参),由编译器负责分配释放,函数结束,栈变量失效。程序员
(2)堆(Heap):由程序员用malloc/calloc/realloc分配,free释放。若是程序员忘记free了,则会形成内存泄露,程序结束时该片内存会由OS回收。算法
(3)全局区/静态区(Global Static Area): 全局变量和静态变量存放区,程序一经编译好,该区域便存在。而且在C语言中初始化的全局变量和静态变量和未初始化的放在相邻的两个区域(在C++中,因为全局变量和静态变量编译器会给这些变量自动初始化赋值,因此没有区分了)。因为全局变量一直占据内存空间且不易维护,推荐少用。程序结束时释放。数组
(4)C风格字符串常量存储区: 专门存放字符串常量的地方,程序结束时释放。数据结构
(5)程序代码区:存放程序二进制代码的区域。app
二:C++中的内存机制函数
在C++语言中,与C相似,不过也有所不一样,内存主要分为以下5个存储区:post
(1)栈(Stack):位于函数内的局部变量(包括函数实参),由编译器负责分配释放,函数结束,栈变量失效。优化
(2)堆(Heap):这里与C不一样的是,该堆是由new申请的内存,由delete或delete[]负责释放
(3)自由存储区(Free Storage):由程序员用malloc/calloc/realloc分配,free释放。若是程序员忘记free了,则会形成内存泄露,程序结束时该片内存会由OS回收。
(4)全局区/静态区(Global Static Area): 全局变量和静态变量存放区,程序一经编译好,该区域便存在。在C++中,因为全局变量和静态变量编译器会给这些变量自动初始化赋值,因此没有区分了初始化变量和未初始化变量了。因为全局变量一直占据内存空间且不易维护,推荐少用。程序结束时释放。
(5)常量存储区: 这是一块比较特殊的存储区,专门存储不能修改的常量(若是采用非正常手段更改固然也是能够的了)。
三:堆和栈的区别
3.1 栈(Stack)
具体的讲,现代计算机(冯诺依曼串行执行机制),都直接在代码低层支持栈的数据结构。这体如今有专门的寄存器指向栈所在的地址(SS,堆栈段寄存器,存放堆栈段地址);有专门的机器指令完成数据入栈出栈的操做(汇编中有PUSH和POP指令)。
这种机制的特色是效率高,但支持数据的数据有限,通常是整数、指针、浮点数等系统直接支持的数据类型,并不直接支持其余的数据结构(能够自定义栈结构支持多种数据类型)。由于栈的这种特色,对栈的使用在程序中是很是频繁的 。对子程序的调用就是直接利用栈完成的。机器的call指令里隐含了把返回地址入栈,而后跳转至子程序地址的操做,而子程序的ret指令则隐含从堆栈中弹出返回地址并跳转之的操做。
C/C++中的函数自动变量就是直接使用栈的例子,这也就是为何当函数返回时,该函数的自动变量自动失效的缘由,于是要避免返回栈内存和栈引用,以避免内存泄露。
3.2 堆(Heap)
和栈不一样的是,堆得数据结构并非由系统(不管是机器硬件系统仍是操做系统)支持的,而是由函数库提供的。基本的malloc/calloc/realloc/free函数维护了一套内部的堆数据结构(在C++中则增长了new/delete维护)。
当程序用这些函数去得到新的内存空间时,这套函数首先试图从内部堆中寻找可用的内存空间(常见内存分配算法有:首次适应算法、循环首次适应算法、最佳适应算法和最差适应算法等。os的基本内容!!)。若是没有可用的内存空间,则试图利用系统调用来动态增长程序数据段的内存大小,新分配获得的空间首先被组织进内部堆中去,而后再以适当的形式返回给调用者。当程序释放分配的内存空间时,这片内存空间被返回到内部堆结构中,可能会被适当的处理(好比空闲空间合并成更大的空闲空间),以更适合下一次内存分配申请。 这套复杂的分配机制实际上至关于一个内存分配的缓冲池(Cache),使用这套机制有以下几个缘由:
(1)系统调用可能不支持任意大小的内存分配。有些系统的系统调用只支持固定大小及其倍数的内存请求(按页分配);这样的话对于大量的小内存分配来讲会形成浪费。
(2)系统调用申请内存多是代价昂贵的。 系统调用可能涉及到用户态和核心态的转换。
(3)没有管理的内存分配在大量复杂内存的分配释放操做下很容易形成内存碎片。
3.3 栈和堆的对比
从以上介绍中,它们有以下区别:
(1)栈是系统提供的功能,特色是快速高效,缺点是由限制,数据不灵活;
堆是函数库提供的功能,特色是灵活方便,数据适应面广,可是效率有必定下降。
(2)栈是系统数据结构,对于进程/线程是惟一的;
堆是函数库内部数据结构,不必定惟一,不一样堆分配的内存没法互相操做。
(3)栈空间分静态分配和动态分配,通常由编译器完成静态分配,自动释放,栈的动态分配是不被鼓励的;
堆得分配老是动态的,虽然程序结束时全部的数据空间都会被释放回系统,可是精确的申请内存/释放内存匹配是良好程序的基本要素。
(4)碎片问题
对于堆来说,频繁的new/delete等操做势必会形成内存空间的不连续,从而形成大量的碎片,使程序的效率下降;对于栈来说,则不会存在这个问题,由于栈是后进先出(LIFO)的队列。
(5)生长方向
堆的生长方向是向上的,也就是向这内存地址增长的方向;对于栈来说,生长方向倒是向下的,是向着内存地址减小的方向增加。
(6)分配方式
堆都是动态分配的,没有静态分配的堆;
栈有两种分配方式:静态分配和动态分配。静态分配是编译器完成的,好比局部变量的分配。动态分配则由alloca函数进行分配,可是栈的动态分配和堆不一样,它的动态分配是由编译器进行释放,无需咱们手工实现。
(7)分配效率
栈是机器系统提供的数据结构,计算机在底层提供支持,分配有专门的堆栈段寄存器,入栈出栈有专门的机器指令,这些都决定了栈的高效率执行。
堆是由C/C++函数库提供的,机制比较复杂,有不一样的分配算法,易产生内存碎片,须要对内存进行各类管理,效率比栈要低不少。
四:具体实例分析
例子(一)
看下面的一小段C程序,仔细体会各类内存分配机制。
int a = 0; //全局初始化区,a的值为0 char *p1; //全局未初始化区(C++中则初始化为NULL) int main() { int b; //b分配在栈上,整型 char s[] = "abc"; //s分配在栈上,char *类型;"abc\0"分配在栈上,运行时赋值,函数结束销毁 char *p2; //p2分配在栈上,未初始化 char *p3 = "123456"; //p3指向"123456"分配在字符串常量存储区的地址,编译时肯定 static int c = 0; //c在全局(静态)初始化区,能够屡次跨函数调用而保持原值 p1 = (char *)malloc(10); //p1在全局未初始化区,指向分配得来得10字节的堆区地址 p2 = (char *)malloc(20); //p2指向分配得来得20字节的堆区地址 strcpy(p1, "123456"); //"123456"放在字符串常量存储区,编译器可能会将它与p3所指向的"123456"优化成一块 return 0; }
例子(二)
看下面的一小段代码,体会堆与栈的区别:
int foo() { //其他代码 int *p = new int[5]; //其他代码 return 0; }
其中的语句int *p = new int[5];就包含了堆与栈。其中new关键字分配了一块堆内存,而指针p自己所占得内存为栈内存(通常4个字节表示地址)。这句话的意思是在栈内存中存放了一个指向一块堆内存的指针p。在程序中先肯定在堆中分配内存的大小,而后调用new关键字分配内存,最后返回这块内存首址,放入栈中。汇编代码为:
int foo() { 008C1520 push ebp 008C1521 mov ebp,esp 008C1523 sub esp,0D8h 008C1529 push ebx 008C152A push esi 008C152B push edi 008C152C lea edi,[ebp-0D8h] 008C1532 mov ecx,36h 008C1537 mov eax,0CCCCCCCCh 008C153C rep stos dword ptr es:[edi] int *p = new int[5]; 008C153E push 14h 008C1540 call operator new[] (8C1258h) 008C1545 add esp,4 008C1548 mov dword ptr [ebp-0D4h],eax 008C154E mov eax,dword ptr [ebp-0D4h] 008C1554 mov dword ptr [p],eax return 0; 008C1557 xor eax,eax } 008C1559 pop edi 008C155A pop esi 008C155B pop ebx 008C155C add esp,0D8h 008C1562 cmp ebp,esp 008C1564 call @ILT+395(__RTC_CheckEsp) (8C1190h) 008C1569 mov esp,ebp 008C156B pop ebp 008C156C ret
若是须要释放内存,这里咱们须要使用delete[] p,告诉编译器,我要删除的是一个数组。
例子(三)
看下面的一小段代码,试着找出其中的错误:
#include <iostream>
using namespace std; int main() { char a[] = "Hello"; // 分配在栈上 a[0] = 'X'; cout << a << endl; char *p = "World"; // 分配在字符串常量存储区的地址 p[0] = 'X'; cout << p << endl; return 0; }
发现问题了吗?是的,字符数组a的容量是6个字符,其内容为"hello\0"。a的内容时能够改变的,好比a[0]='X',由于其是在栈上分配的,也就是在运行时肯定的内容。可是指针p指向的字符串"world"分配在字符串常量存储区,内容为"world\0",常量字符串的内容时不能够修改的。从语法上来讲,编译器并不以为语句p[0]='X'有什么问题,可是在运行时则会出现"access violation"非法内存访问的问题。
如下几个函数的变化要看清楚了:
char *GetString1(void) { char p[] = "hello,world"; //结果:h。因为数组指针指向第一元素的地址,因此调用以后是h return p; } char *GetString2(void) { char *p = "hello,world"; //结果:hello,world。因为p指向“hello,world”字符串常量区域地址 return p; } char *GetString3(void) { char *p = (char *)malloc(20); // 指向p所分配的堆上的内存空间。 return p; } char *GetString4(void) { char *p = new char[20]; // 指向p所分配的内存空间,p自己在栈上的,p所指向的空间是堆上的。 return p; }
附录:内存管理注意事项
【规则1】用malloc或new申请内存以后,应该当即检查指针值是否为NULL,防止使用指针值为NULL的内存,能够在函数入口处断言检测。
【规则2】不要忘记为数组或动态内存赋初值(好比calloc比malloc就要好),指针初始化为NULL(c++中为0)。
【规则3】避免数组或指针下标越界,特别小心发生“多1”或者"少1"
的操做。
【规则4】动态内存的申请和释放必须配对,防止内存泄露,具体为malloc/calloc/realloc和free配对,new和delete以及delete[]配对。
【规则5】用free或者delete释放内存后,应当即将指针设置为NULL(C++中为0),防止产生“野指针”、"悬垂指针"。
【规则6】遇到不懂得问题及时debug,通常的虫子debug一下就灰飞烟灭了,一切bug都是浮云而已