转自:http://www.cnblogs.com/ComputerG/archive/2012/02/01/2334898.html

一:C语言中的内存机制

在C语言中,内存主要分为如下5个存储区:

(1)栈(Stack):位于函数内的局部变量包括函数实参),由编译器负责分配释放,函数结束,栈变量失效。

(2)堆(Heap):由程序员用malloc/calloc/realloc分配,free释放。如果程序员忘记free了,则会造成内存泄露,程序结束时该片内存会由OS回收。

(3)全局区/静态区(Global Static Area): 全局变量和静态变量存放区,程序一经编译好,该区域便存在。并且在C语言中初始化的全局变量和静态变量和未初始化的放在相邻的两个区域(在C++中,由于全局变量和静态变量编译器会给这些变量自动初始化赋值,所以没有区分了)。由于全局变量一直占据内存空间且不易维护,推荐少用。程序结束时释放。

(4)C风格字符串常量存储区: 专门存放字符串常量的地方,程序结束时释放

(5)程序代码区:存放程序二进制代码的区域


:C++中的内存机制

在C++语言中,与C类似,不过也有所不同,内存主要分为如下5个存储区:

(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都是浮云而已。

c/c++内存机制(一)(转)的更多相关文章

  1. java基础知识(四)java内存机制

    Java内存管理:深入Java内存区域 上面的文章对于java的内存管理机制讲的非常细致,在这里我们只是为了便于后面内容的理解,对java内存机制做一个简单的梳理. 程序计数器:当前线程所执行的字节码 ...

  2. 图解VMware内存机制

    在写<VMware内存机制初探>之后,原本是计划写一篇<VMware内存机制再探>的,讲一讲VMware内存机制中的另外几个重要内容,比如透明内存共享(TPS, Transpa ...

  3. java 内存机制

    1.Java的内存机制 Java 把内存划分成两种:一种是栈内存,另一种是堆内存.在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配,当在一段代码块定义一个变量时,Java 就在 ...

  4. 「轉」Java的内存机制

    0.参考资料: http://www.j2megame.org/index.php/content/view/2246/125.html 1.Java的内存机制 Java 把内存划分成两种:一种是栈内 ...

  5. Fresco内存机制(Ashmem匿名共享内存)

    Fresco的内存机制 Fresco是Facebook出品的高性能图片加载库,采用了Ashmem匿名共享内存机制, 来解决图片加载中的OOM问题.这里不对Fresco做深入分析,只关注Fresco在A ...

  6. C++ STL中vector的内存机制和性能分析

    vecotr是动态数组,顾名思义他可以动态的增加自己的长度. 内存机制: 但是怎样的增加自己的长度? vector有两个函数一个是capacity()返回内存空间即缓冲区的大小,另一个是size()返 ...

  7. Android内存机制分析1——了解Android堆和栈

    //----------------------------------------------------------------------------------- Android内存机制分析1 ...

  8. 5. c++ 内存管理 C/C++ 内存机制

    参考自:http://blog.csdn.net/wpf_ml/article/details/7759911 1. 内存,Cache,寄存器内存:通常计算机将数据存放在物理内存,cache及寄存器中 ...

  9. Linux 内存机制详解宝典

    Linux 内存机制详解宝典 在linux的内存分配机制中,优先使用物理内存,当物理内存还有空闲时(还够用),不会释放其占用内存,就算占用内存的程序已经被关闭了,该程序所占用的内存用来做缓存使用,对于 ...

随机推荐

  1. Qt入门(16)——组装窗口部件

    这个例子显示了创建几个窗口部件并用信号和槽把它们连接起来,和如何处理重新定义大小事件. #include <qapplication.h> #include <qpushbutton ...

  2. 阿里云ECS试用

    公司在推一个大项目,感觉阿里云挺好用的,自己搞了台小机器平时可以跑着玩,而且可以做个跳板机,平时学校里的收费网直接用跳板机就可以访问了,直接写个脚本在自己机器上跑一下: #!/usr/bin/expe ...

  3. Binary Tree Inorder Traversal ——LeetCode

    Given a binary tree, return the inorder traversal of its nodes' values. For example:Given binary tre ...

  4. LeetCode (85): Maximal Rectangle [含84题分析]

    链接: https://leetcode.com/problems/maximal-rectangle/ [描述] Given a 2D binary matrix filled with '0's ...

  5. HDU2059(龟兔赛跑)

    龟兔赛跑 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submi ...

  6. poj 1609 dp

    题目链接:http://poj.org/problem?id=1609 #include <cstdio> #include <cstring> #include <io ...

  7. Java同步工具类总结

    先谈谈闭锁和栅栏的区别: 1.关键区别在于,所有线程必须同时到达栅栏位置,才能继续执行. 2.闭锁用于等待某一个事件的发生,举例:CountDownLatch中await方法等待计数器为零时,所有事件 ...

  8. TCP/IP协议族-----10、搬家IP

  9. MYSQL 体系结构图-space结构图

  10. (转)void指针(void *的用法)

    指针有两个属性:指向变量/对象的地址和长度 但是指针只存储地址,长度则取决于指针的类型 编译器根据指针的类型从指针指向的地址向后寻址 指针类型不同则寻址范围也不同,比如: int*从指定地址向后寻找4 ...