转自:http://blog.csdn.net/zwan0518/article/details/9040467
本文主要分为三个部分。第一部分是对linux的内存做一下简单介绍。第二部分是对C和C++内存管理的几个关键字做一些介绍和它们的不同。第三部分是参考资料。
- 第一部分
这一部分包含有两部分内容。第一部分是在Linux中内存的分配;第二部分是内存的管理。
内存分配:
在linux中对内存的申请是做如下操作。当申请成功的时候,只是返回的逻辑地址,即从某个地址到某个地址区间的内存你可以使用。这个时候物理内存是没有分配,只有你做真实的写操作的时候,真正的物理内存才分配下来。
而且当申请内存的时候,一般会分配比申请内存要大的一块内存区域,把这块内存缓冲下来。这样下次申请的的时候就可以在缓存内首先查找,而不需要去操作系统申请。同样在内存释放的时候,也不会立即把内存释放给操作系统,也是缓冲下来。这也可以解释为何有些时候申请内存的时候,内存里会有乱七八糟的数据。这就是因为少一次申请内存中的数据么有被清零,而这块内存又被缓冲下来。
对于linux中内存分配如下图所示:
其中在可以看出对于32位机器,可以用4G内存可用。其中1G内存是操作系统保留,我们可用内存是3G内存。对于内存主要分为
代码段:是从0地址开始,主要存放编译好的代码。
数据段:代码段上面就是数据段,存放程序中已经初始化的数据,包括静态变量,final变量等一些全局变量。它是属于静态内存分配,是不参与程序运行期的动态分配。
BSS(Block Started by Symbo):存放声明但是没有初始化的全局变量。
堆与栈。我们程序中经常提到,或者我们可以直接使用的主要是两部分stack与heap,即我们平时提到的栈内存与堆内存。请java程序员不要把这里的栈内存与堆内存和java中的内存管理中的名词相混淆。栈内存是从高地址向低地址增长,堆内存是从低地址向高地址增长。
而对于堆与栈之间的mmap内存区域,它其实和堆栈是差不多的,也是程序运行期动态分配内存。它们两个不同的是,当申请对象不同的时候,分配到不同的区域。当对象是个小对象生命周期比较短的时候,则在堆内存分配;对象比较大而且生命周期比较长,那么就在mmap区域分配。一般通过系统调用mmap()方法申请内存,可以把一个文件映射到内存中,通过访问内存的方式访问文件。
另外,很多申请内存的方式底层一般都是调用的mmap()系统调用。
内存管理:
对于内存的管理,Linux是通过虚拟地址映射让进程可以不由考虑内存的限制,通过进程的换入和换出来实现内存的共享(请查看内存swap介绍),虚拟地址到物理地址的映射通过段页机制进行管理。每个进程或者作业拥有一张段表,每一个段拥有一个页表用来把段内地址通过页表映射到物理地址空间。
- 第二部分:
C语言不同于Java。Java在需要对象的时候直接new一个出来就好了,使用完之后一般不需要考虑内存释放的问题,GC会负责垃圾回收的善后工作。它的回收机制也有很多种,可以参考底部的参考资料。但是它的善后工作做的并不是万无一失,内存回收失败的场景会有很多种,这个不是我们这篇文章来考虑的了。在C语言中,想使用内存必须自己去申请,然后在使用结束之后再把内存手动回收,否则这些内存就会导致内存泄漏。
在C语言中内存申请有三种方式,不同方式有不同的应用场景。下面就主要说一下不同方法之间的不同。
1) malloc 函数: void *malloc(unsigned int size)
在内存的动态分配区域中分配一个长度为size的连续空间,如果分配成功,则返回所分配内存空间的首地址,否则返回NULL,申请的内存不会进行初始化。malloc在底层会根据具体情况进行系统调用brk或者mmap
2)calloc 函数: void *calloc(unsigned int num, unsigned int size)
按照所给的数据个数和数据类型所占字节数,分配一个 num * size 连续的空间。calloc申请内存空间后,会自动初始化内存空间为 0,但是malloc不会进行初始化,其内存空间存储的是一些随机数据。
3)realloc 函数: void *realloc(void *ptr, unsigned int size)
给一个原有的指针ptr动态分配一个长度为size的内存空间,并把内存空间的首地址赋值给ptr,把ptr内存空间调整为size。申请的内存空间不会进行初始化。realloc会释放传进去的内存,返回新分配的内存。
另外,当申请的size比原来的小的时候将返回空指针。当申请的比原来大的时候,分为两种情况
下面给出代码运行效果
- char *p = (char *) malloc(SIZE * sizeof(char));
- printf("%s\n", p);
- p = (char *) calloc(1, SIZE * sizeof(char));
- printf("%s\n", p);
- p = "a";
- printf("%s\n", p);
输出结果:**(乱码)空行 a。另外,在分配内存之后,记得释放资源
在这里还想在着重说一下realloc的其他需要注意的地方。第一点:一般realloc是用来扩大原来的内存大小,所以第二个参数size大小一般要大于原来大小,否则会因为编译器不同而出现分配失败或者报错现象;第二点:第一个参数指针可以为null,这时候realloc就和malloc使用一样,但是分配的内存是初始化了的;第三点:该方法扩大内存的时候,首先会在原有内存底部开始扩展,如果底部内存满足要求,则直接向下扩展,这时候新返回的内粗地址与原有的内粗起始地址相同,如果底部内存不满足要求,则把原有内存的数据一同拷贝到新的位置,这时候新返回的指针地址和原有地址就不同了,如果没有满足要求的内存,那么就返回null了,对原有指针不会产生影响。
对于C++语言中,不仅你可以使用malloc,还有new可以申请内存。那么它们之间有什么差别那。
对于malloc是属于标准函数,其申请的内存是没有任何属性的,所以对申请完的内存要进行强制类型转换为你需要的内存类型。new则是C++特有的运算符。malloc无法满足动态管理对象的要求,而C++又是主要面向对象操作的。在对象在生成要自动执行构造函数,对象在消亡之前要自动执行析构函数。对于new操作,其完成了两部分工作,第一部分申请内存,第二部分调用构造函数进行该内存的初始化。所以如果仅使用malloc的话,是无法完成对内存的初始化操作的。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free,所以C++创建了new/delete运算符,用来实现创建对象调用构造,消亡对象调用析构。